Javascript 执行一系列承诺,但等待下一个承诺执行,直到先例解决
我正在构建一个脚本,用于应用在特定文件夹中找到的所有.sql文件。我希望这些脚本一个接一个地执行,如果一个脚本失败,就停止执行。 我的文件夹树是这样的(我通过根文件夹Javascript 执行一系列承诺,但等待下一个承诺执行,直到先例解决,javascript,node.js,Javascript,Node.js,我正在构建一个脚本,用于应用在特定文件夹中找到的所有.sql文件。我希望这些脚本一个接一个地执行,如果一个脚本失败,就停止执行。 我的文件夹树是这样的(我通过根文件夹npm run migrate dev调用脚本) 例如,/migrations中的两个文件可以是 01.sql USE gamehub; CREATE TABLE `user` (`id` CHAR (32) NOT NULL); 02.sql USE gamehub; CREATE TABLE `user` (`id` CH
npm run migrate dev
调用脚本)
例如,/migrations中的两个文件可以是
01.sql
USE gamehub;
CREATE TABLE `user` (`id` CHAR (32) NOT NULL);
02.sql
USE gamehub;
CREATE TABLE `user` (`id` CHAR (32) NOT NULL);
migrate.js
const sqlParams = require('./dbConfig');
const mysql = require('mysql');
const fs = require('fs');
const pool = mysql.createPool(sqlParams);
if (process.env.RUN_MODE) {
console.log(`Running in dev mode`);
}
const migrate = () => {
console.log('Running migrations');
// get files list
fs.readdir('./db/migrations', (err, files) => {
if (err) {
console.log('Error while reading files in .db/migrations');
console.log(err);
throw err;
}
// generate promises list
const promises = files.map(
(file) =>
new Promise((resolve, reject) => {
fs.readFile(`./db/migrations/${file}`, 'utf-8', (err, data) => {
if (err) {
console.log(`Error while reading script ${file}`);
reject(err);
}
pool.query(data, (err, results) => {
if (err) {
console.log('Error while querying SQL');
reject(err);
}
resolve();
});
});
})
);
console.log(promises);
promises.reduce(async (prev, promise, index) => {
await prev;
console.log(`Executing script #${index}: ${files[index]}`);
promise.then(
() => Promise.resolve(),
(err) => {
console.log(err);
return;
}
);
}, Promise.resolve());
});
};
migrate();
问题有两个:未发生错误时的输出:
> node -r dotenv/config ./db/migrate.js
Running in dev mode
Running migrations
[ Promise { <pending> }, Promise { <pending> } ]
Executing script #0: 01_selectGameHub.sql
Executing script #1: 02_createUserTable.sql
>节点-r dotenv/config./db/migrate.js
在开发模式下运行
运行迁移
[承诺{},承诺{}]
正在执行脚本#0:01_selectGameHub.sql
正在执行脚本#1:02_createUserTable.sql
发生错误时的输出(例如,如果用户表已存在):
>节点-r dotenv/config./db/migrate.js
在开发模式下运行
运行迁移
[承诺{},承诺{}]
正在执行脚本#0:01_selectGameHub.sql
正在执行脚本#1:02_createUserTable.sql
查询SQL时出错
错误:ER_PARSE_错误:您的SQL语法有错误;请查看与MySQL服务器版本对应的手册,以了解第3行中使用“')”附近的正确语法
在Query.Sequence.\u packetToError
[....]
在Pool.query([..]\node\u modules\mysql\lib\Pool.js:199:23)
在[..]\db\migrate.js:28:18
在FSReqCallback.readFileAfterClose[as oncomplete](内部/fs/read_file_context.js:63:3){
代码:'ER_PARSE_ERROR',
编号:1064,
sqlMessage:“您的SQL语法有错误;请检查与您的MySQL服务器版本相对应的手册,以了解第3行中的“')”附近使用的正确语法”,
sqlState:'42000',
索引:0,
sql:'CREATE TABLE`user`(\n\t`id`CHAR(32)不为空,\n);\n'
}
我要做的第一件事是将这一大块代码分解成更小、更容易理解的函数。你的逻辑将变得非常清晰:
const migrate = async () => {
console.log('Running migrations');
const files = await getFilesList('./db/migrations');
for (let i of files) {
console.log(`Executing script: ${file}`);
const data = await readFile(`./db/migrations/${file}`);
await runQuery(data);
}
};
function getFilesList(dir) {
return new Promise((resolve, reject) => {
fs.readdir(dir, (err, files) => {
if (err) {
console.log('Error while reading files in ' + dir);
console.log(err);
return reject(err);
}
resolve(files);
});
});
}
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf-8', (err, data) => {
if (err) {
console.log(`Error while reading script ${file}`);
return reject(err);
}
resolve(data);
});
});
}
function runQuery() {
return new Promise((resolve, reject) => {
pool.query(data, (err, results) => {
if (err) {
console.log('Error while querying SQL');
return reject(err);
}
resolve();
});
});
}
// Don't forget to close the pool, when you're done, or an error occurred:
migrate().catch(e => console.log(e)).finally(() => pool.end());
您甚至可以使用将回调函数转换为承诺函数来进一步简化,以便代码与承诺保持一致:
const { promisify } = require("util");
const migrate = async() => {
console.log('Running migrations');
const files = await getFilesList('./db/migrations');
for (let file of files) {
console.log(`Executing script: ${file}`);
const data = await readFile(`./db/migrations/${file}`);
await runQuery(data);
}
};
function getFilesList(dir) {
return promisify(fs.readdir)(dir)
.catch(err => {
console.log('Error while reading files in ' + dir);
throw err;
});
}
function readFile(file) {
return promisify(fs.readFile)(file, 'utf-8')
.catch(err => {
console.log(`Error while reading script ${file}`);
throw err;
});
}
function runQuery() {
return promisify(pool.query.bind(pool))(data)
.catch(err => {
console.log('Error while querying SQL');
throw err;
});
}
// Don't forget to close the pool, when you're done, or an error occurred:
migrate().catch(e => console.log(e)).finally(() => pool.end());
有几个问题。第一个是,你正在并行地履行你的承诺,而不是等待之前的承诺兑现。然后,我看不到关闭连接池的任何行(这就是为什么脚本不会自行停止,您的DB连接仍然可以接受查询)。From:
pool.end(err=>{/*池中的所有连接都已结束*/})代码>我认为等待prev
足以阻止在reduce中运行并行承诺,这将在for
循环中运行,但在reduce
中不起作用,特别是当您在进入还原之前在映射中启动它们时,什么是完成任务的正确方法?@ZenoDallaValle当您创建承诺并调用池时,任务将执行。查询,而不是在等待之后执行。如果希望按顺序执行这些操作,请不要使用files.map(…)
。readdir/readFile
具有sync
副本,这些副本在此处可能会派上用场。另外,let file of files
而不是let i in files
会更好。“使用像es6 promisify这样的包”-为什么不只是nodejs自带的util.promisify
?另外,fs.promises
…好的@Bergi,我两个都修好了。我不知道promisify的原生版本存在!当您执行promisify(fs.readdir)
时,没有范围问题,因为fs
是一个简单的对象。但是pool
是类的特定实例,不将上下文绑定到它(确保this
引用pool
)将导致范围问题,因为query
方法需要知道要使用哪个实例。只要调用pool.query(…)
,这不是问题。但当您将其作为回调传递给promisify
时,它将丢失其context@ZenoDallaValle这可能有助于您理解: