node.js - 理解和实现 Promise 和异步事物如何与循环一起工作存在问题

标签 node.js asynchronous ecmascript-6 promise callback

我正在学习 Node ,我有一段代码可以查询数据库中的数据,我想将其传递给回调函数。但我只想返回唯一的成员。

这意味着我必须遍历查询结果,并将唯一成员推送到数组中,然后将该数组传递到 cb 函数中。

问题是,输入也是我构建查询的数组。所以那里已经存在一个循环。

我已经查看了一些以前的带有 Promise、await 等的 SO 线程。但是我在使用像这里这样的循环来实现它们时遇到了一些麻烦。

static getDestination(envName, cb){
    var ans = new Array();
    var promises;
    DataBase.initDB((db) => {
        for (var i = 0; i < envName.length; i++){
            db.collection('Environment').find({'environmentName' : envName[i]}, (err, data) => {
                if (err) {
                    console.log('error getting data in getDestination()');
                    return;
                }
                data.toArray((err, content) => {
                    if (err) {
                        console.log('error converting cursor into array');
                        return;
                    }
                    else {
                        for (const doc of content){
                            if (!ans.includes(doc.destination)){
                                ans.push(doc.destination);
                            }
                            //cb(ans); 
                        }
                    }
                }); 
            });
        }
    })
    cb(ans); 
}

现在,回调只是一个 console.log(ans),它打印出一个空数组 []。

如果我在最内层循环中调用回调,它会返回正确的成员,但它会随着循环的进行而被调用:

[]
[ 'DEV' ]
[ 'DEV' ]
[ 'DEV', 'QA' ]

我只想获取输出的最后一行。彻底的解释也会令人惊奇!

编辑:根据我的理解,我应该创建一个 promise 数组,并且在循环的每次迭代中,我应该创建一个新的 promise ,并将操作作为参数。然后在一切结束时,我可以打电话

Promise.all(promises).then(cb(ans));

但是我无法理解应该在哪笔交易上创建 promise 。因为如果我在最内层循环中创建 Promise,则循环外部的 cb 在第一个循环创建之前就会被调用。

编辑:

exports.initDB = (cb) => {
console.log('Connecting...');
const uri = <mongo connection uri here>;
const client = new MongoClient(uri, { useNewUrlParser: true });
client.connect(err => {
    if (err) {
        console.log('eror connecting to db');
        console.log(err);
        return;
    }
    cb(client.db(process.env.DB_NAME));
});

}

最佳答案

回调

以下是创建 Promise 数组并使用它来聚合 ans 的方法。然后调用你的cb()一次。我还建议使用the Node.js callback conventioncb(error, result)而不是cb(result) ,因为有多个步骤可能会出错,并且 getDestination() 的调用者当查询成功或失败时都应该收到通知。

static getDestination (envName, cb) {
  DataBase.initDB(db => {
    const envCollection = db.collection('Environment');
    const promises = envName.map(value => new Promise((resolve, reject) => {
      envCollection.find({ environmentName: value }, (error, data) => {
        if (error) return reject(error);

        data.toArray((error, content) => {
          if (error) reject(error);
          else resolve(content);
        });
      });
    }));

    Promise.all(promises).then(contents => {
      const ans = contents.reduce(
        (ans, content) => content.reduce(
          (ans, doc) => ans.add(doc.destination),
          ans
        ),
        new Set()
      );

      cb(null, [...ans]);
    }).catch(error => {
      cb(error);
    });
  });
}

请注意,每个 new Promise() 同步构造,并且resolve()reject()被异步调用。这是必要的,因为您的数据库查询尚未返回 Promise .

如果您使用返回 Promise 的 API,则应避免 explicit promise construction antipattern并从现有的 promise 中链接出来。

我还优化了 ans 的聚合通过使用 Set 而不是数组,因此我们实际上可以省略对现有值的检查,因为与数组不同,集合只保留每个唯一值之一。

promise 链

如果你想要你的getDestination()函数返回一个 promise 而不是接受回调,你可以像这样重写它:

static getDestination (envName) {
  return new Promise(resolve => {
    DataBase.initDB(resolve);
  }).then(db => {
    const envCollection = db.collection('Environment');
    const promises = envName.map(value => new Promise((resolve, reject) => {
      envCollection.find({ environmentName: value }, (error, data) => {
        if (error) return reject(error);

        data.toArray((error, content) => {
          if (error) reject(error);
          else resolve(content);
        });
      });
    }));

    return Promise.all(promises);
  }).then(contents => {
    const ans = contents.reduce(
      (ans, content) => content.reduce(
        (ans, doc) => ans.add(doc.destination),
        ans
      ),
      new Set()
    );

    return [...ans];
  });
}

主要区别是:

  • 包裹DataBase.initDB()在另一个Promise因为结果取决于db
  • getDestination() 返回 promise 链
  • 返回[...ans]来自.then()而不是将其传递给回调
  • 删除 .catch()并让调用者处理任何错误

请注意,因为我们 return Promises.all(promises)来自.then() ,我们可以得到contents来自外部 promise ,而不是链接到内部 Promise.all() 。这是 promise 提供逃离 callback hell 的主要好处之一.

异步/等待

最后,如果您想使用 async / await 而不是.then() ,你可以像这样重写一次:

static async getDestination (envName) {
  const db = await new Promise(resolve => {
    DataBase.initDB(resolve);
  });

  const envCollection = db.collection('Environment');
  const promises = envName.map(value => new Promise((resolve, reject) => {
    envCollection.find({ environmentName: value }, (error, data) => {
      if (error) return reject(error);

      data.toArray((error, content) => {
        if (error) reject(error);
        else resolve(content);
      });
    });
  }));

  const contents = await Promise.all(promises);
  const ans = contents.reduce(
    (ans, content) => content.reduce(
      (ans, doc) => ans.add(doc.destination),
      ans
    ),
    new Set()
  );

  return [...ans];
}

这里的主要区别在于每个

return somePromise.then(someVariable => { ... });

被替换为

const someVariable = await somePromise;
...

模块化

附注如果你想从 getDestination() 中删除构造的 promise 函数,您可以将代码重构为两个辅助函数,可以像这样使用:

static getCollection (collectionName) {
  return new Promise(resolve => {
    DataBase.initDB(resolve);
  }).then(
    db => db.collection(collectionName)
  );
}

static getDocuments (collection, ...args) {
  return new Promise((resolve, reject) => {
    collection.find(...args, (error, data) => {
      if (error) return reject(error);

      data.toArray((error, content) => {
        if (error) reject(error);
        else resolve(content);
      });
    });
  });
}

static async getDestination (envName) {
  const envCollection = await this.getCollection('Environment');
  const promises = envName.map(
    environmentName => this.getDocuments(
      envCollection,
      { environmentName }
    )
  );

  const contents = await Promise.all(promises);
  const ans = contents.reduce(
    (ans, content) => content.reduce(
      (ans, doc) => ans.add(doc.destination),
      ans
    ),
    new Set()
  );

  return [...ans];
}

它可能比以前稍微多一些代码,但是现在您有两个可重用的函数,可以从除 getDestination() 之外的其他类型的查询中调用。 .

关于node.js - 理解和实现 Promise 和异步事物如何与循环一起工作存在问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57118724/

相关文章:

reactjs - 在 Redux-saga 中使用 Fetch 发布表单数据

javascript - 地理定位真的很慢我做错了什么?

javascript - 在 ES 6/Harmony 中拆分类定义

javascript - 从 HTTP GET 请求中获取数据

node.js - Nock + 多部分表单数据 = 请求不匹配

javascript - 如何等待一组异步回调函数?

asynchronous - 使用 Combine 和 SwiftUI 的异步操作

javascript - 如何 promise 这段 Mongoose 代码?

javascript - 使用 Sequelize + multer 在 SQL 中记录图像名称的问题

javascript - 如何选择通过 jQuery load() 函数加载的元素?