异步子任务的异步光标迭代

时间:2018-04-16 02:33:00

标签: javascript node.js mongodb

我想对没有数字键(_id)的mongoDB集合执行迭代。该集合只有随机字符串作为_id,并且集合的大小很大,因此使用.toArray()在RAM上加载整个文档不是一个可行的选择。另外,我想对每个元素执行异步任务。由于任务的异步性质,.map().each().forEach()的使用受到限制。我尝试使用那些提到的方法运行任务,但它当然与异步任务冲突,返回待处理的承诺而不是正确的结果。

示例

async function dbanalyze(){

  let cursor = db.collection('randomcollection').find()
  for(;;){
    const el = cursor.hasNext() ? loaded.next() : null;
    if(!cursor) break
    await performAnalyze(cursor) // <---- this doesn't return a document but just a cursor object
  }

}

如何仅使用for()迭代mongoDB集合?

1 个答案:

答案 0 :(得分:4)

Cursor.hasNext()方法也是“异步”的,因此您也需要await。同样适用于Cursor.next()。因此,实际的“循环”用法确实应该是while

async function dbanalyze(){

  let cursor = db.collection('randomcollection').find()
  while ( await cursor.hasNext() ) {  // will return false when there are no more results
    let doc = await cursor.next();    // actually gets the document
    // do something, possibly async with the current document
  }

}

如评论中所述,当光标实际耗尽时,Cursor.hasNext()最终将返回false,而Cursor.next()实际上是从光标中检索每个值。当breakhasNext()时,您可以执行其他结构和false循环,但它更自然地适用于while

这些仍然是“异步”,因此您需要await每个人的承诺解决方案,这是您缺少的主要事实。

至于Cursor.map(),你可能会错过在提供的函数上用async标记标记的点:

 cursor.map( async doc => {                   // We can mark as async
    let newDoc = await someAsyncMethod(doc);  // so you can then await inside
    return newDoc;
 })

但你实际上仍然希望在某处“迭代”,除非你可以将.pipe()用于其他输出目的地。

async/await标志也使Cursor.forEach() “更实用”,因为它的一个常见缺陷是无法简单地处理“内部”异步调用,但是使用这些标志你现在可以轻松地这样做了,但不可否认,因为你必须使用回调,你可能想把它包装在Promise中:

await new Promise((resolve, reject) => 
  cursor.forEach(
    async doc => {                              // marked as async
      let newDoc = await someAsyncMethod(doc);  // so you can then await inside
      // do other things
    },
    err => {
      // await was respected, so we get here when done.
      if (err) reject(err);
      resolve();
    }
  )
);

当然,总有一些方法可以应用回调或普通的Promise实现,但它是async/await的“糖”,实际上使它看起来更清晰。

NodeJS v10.x和MongoDB节点驱动程序3.1.x及更高版本

最喜欢的版本使用AsyncIterator,现在已在NodeJS v10及更高版本中启用。这是一种更清晰的迭代方式

async function dbanalyze(){

  let cursor = db.collection('randomcollection').find()
  for await ( let doc of cursor ) {
    // do something with the current document
  }    
}

哪种“在某种程度上”回到最初询问的关于使用for循环的问题,因为我们可以在此处执行for-await-of语法以支持iterable支持正确的界面。 Cursor确实支持此界面。

如果你是古玩,这里是我前段时间制作的一个列表,用于展示各种光标迭代技术。它甚至包括Async Iterators from a generator function的案例:

const Async = require('async'),
      { MongoClient, Cursor } = require('mongodb');

const testLen = 3;
(async function() {

  let db;

  try {
    let client = await MongoClient.connect('mongodb://localhost/');

    let db = client.db('test');
    let collection = db.collection('cursortest');

    await collection.remove();

    await collection.insertMany(
      Array(testLen).fill(1).map((e,i) => ({ i }))
    );

    // Cursor.forEach
    console.log('Cursor.forEach');
    await new Promise((resolve,reject) => {
      collection.find().forEach(
        console.log,
        err => {
          if (err) reject(err);
          resolve();
        }
      );
    });

    // Async.during awaits cursor.hasNext()
    console.log('Async.during');
    await new Promise((resolve,reject) => {

      let cursor = collection.find();

      Async.during(
        (callback) => Async.nextTick(() => cursor.hasNext(callback)),
        (callback) => {
          cursor.next((err,doc) => {
            if (err) callback(err);
            console.log(doc);
            callback();
          })
        },
        (err) => {
          if (err) reject(err);
          resolve();
        }
      );

    });

    // async/await allows while loop
    console.log('async/await while');
    await (async function() {

      let cursor = collection.find();

      while( await cursor.hasNext() ) {
        let doc = await cursor.next();
        console.log(doc);
      }

    })();

    // await event stream
    console.log('Event Stream');
    await new Promise((end,error) => {
      let cursor = collection.find();

      for ( let [k,v] of Object.entries({ end, error, data: console.log }) )
        cursor.on(k,v);
    });

    // Promise recursion
    console.log('Promise recursion');
    await (async function() {

      let cursor = collection.find();

      function iterate(cursor) {
        return cursor.hasNext().then( bool =>
          (bool) ? cursor.next().then( doc => {
            console.log(doc);
            return iterate(cursor);
          }) : Promise.resolve()
        )
      }

      await iterate(cursor);

    })();

    // Uncomment if node is run with async iteration enabled
    // --harmony_async_iteration


    console.log('Generator Async Iterator');
    await (async function() {

      async function* cursorAsyncIterator() {
        let cursor = collection.find();

        while (await cursor.hasNext() ) {
          yield cursor.next();
        }

      }

      for await (let doc of cursorAsyncIterator()) {
        console.log(doc);
      }

    })();


    // This is supported with Node v10.x and the 3.1 Series Driver
    await (async function() {

      for await (let doc of collection.find()) {
        console.log(doc);
      }

    })();

    client.close();

  } catch(e) {
    console.error(e);
  } finally {
    process.exit();
  }

})();