Javascript 执行一系列承诺,但等待下一个承诺执行,直到先例解决

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

我正在构建一个脚本,用于应用在特定文件夹中找到的所有.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` 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();
问题有两个:

  • 此代码未结束,我必须使用CTRL+C手动结束它
  • 如果发生错误,执行不会停止

  • 未发生错误时的输出:
    > 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这可能有助于您理解: