在带有process.nextTick()的递归函数中使用异步等待

时间:2019-04-17 19:05:19

标签: node.js async-await lifecycle event-loop

我在使用Promises和process.nextTick()回调的node.js应用程序中有一个有效的递归函数。我很好奇这如何/可以与异步等待一起工作。

我尝试了一些不同的操作,但是无论我做了什么,异步功能都会在所有nextTick回调完成之前返回到调用函数。

不起作用(从快速路由中删除删除缓存)

const deleteCache = async () => {
  try {
    const cacheRef = fsDb.collection('Cache');
    return await deleteDocsBatch(cacheRef, 30);
  } catch (e) {
    console.error('error in deleteCache:' + e);
  }
};

const deleteDocsBatch = async (cacheRef, batchSize) => {
  try {
    // get all the cached docs, limit to 30 to avoid potential memory issues
    const snapShot = await cacheRef.limit(batchSize).get();
    if (snapShot.size === 0) { return; }

    const batch = fsDb.batch();
    snapShot.docs.forEach((doc) => {
      batch.delete(doc.ref);
    });

    await batch.commit();
    process.nextTick(() => {
      deleteDocsBatch(cacheRef, batchSize);
    });
  } catch (e) {
    console.error('error in deleteDocsBatch:' + e);
  }
};

工作:

function deleteCollection (batchSize) {
  var collectionRef = fsDb.collection('Cache');
  var query = collectionRef.orderBy('__name__').limit(batchSize);

  return new Promise((resolve, reject) => {
    deleteQueryBatch(fsDb, query, batchSize, resolve, reject);
  });
}

function deleteQueryBatch (db, query, batchSize, resolve, reject) {
  query.get()
    .then((snapshot) => {
      // When there are no documents left, we are done
      if (snapshot.size === 0) {
        return new Promise((resolve, reject) => { resolve(0); });
      }

      // Delete documents in a batch
      var batch = db.batch();
      snapshot.docs.forEach((doc) => {
        batch.delete(doc.ref);
      });

      return new Promise((resolve, reject) => {
        batch.commit().then(() => {
          resolve(snapshot.size);
        })
          .catch(reject);
      });
    }).then((numDeleted) => {
      if (numDeleted === 0) {
        resolve();
        return;
      }

      // Recurse on the next process tick, to avoid
      // exploding the stack.
      process.nextTick(() => {
        deleteQueryBatch(db, query, batchSize, resolve, reject);
      });
    })
    .catch(reject);
}

是否可以使用async await使用nexttick()编写此递归函数?

原始的Firestore代码示例:

https://firebase.google.com/docs/firestore/manage-data/delete-data

1 个答案:

答案 0 :(得分:-1)

好吧,所以利用以下事实:当您通过.then()将任何内容附加到Promise时,它将在下一个刻度中运行。换句话说,您的process.nextTick甚至在原始代码中也没有必要。在最坏的情况下,您将进入递归调用,但立即退出。永远不要超过深度1。

await.then()的语法糖。无论如何,代码都会转换为.then()系列。所以这个

await deleteDocsBatch(cacheRef, batchSize);

代替

process.nextTick(() => {
  deleteDocsBatch(cacheRef, batchSize);
});

应该足够。尽管如上所述,该函数的初始同步部分(即直到第一次等待)将递归运行。因此,如果您确实要确定,则可以通过

await Promise.resolve();
await deleteDocsBatch(cacheRef, batchSize);

问题在于您告诉解释器“嘿,这是一个异步点,请执行其他操作,像什么都不做,好吗?”。

还要注意,await new Promise(res => process.nextTick(res));是替代方法。虽然太过分了。


一个例子:

async function p1() {
    console.log('interrupt');
};

async function p2() {
    console.log('1');
    await Promise.resolve();
    console.log('2');
};

p2();
p1();

因此您可以看到这两个功能实际上是同步的。 p2并不是因为里面有await。并且await强制将下面的所有内容移至下一个刻度,从而允许p1在两者之间运行。 p2功能等效于

function p2() {
    console.log('1');
    return Promise.resolve().then(() => {
        console.log('2');
    });
};

,输出为:

1
interrupt
2

另一个例子。这非常快地超过了最大递归深度:

async function go(i)
{
  console.log(i);
  go(i+1);
}

go(0);

不是。曾经。

async function go(i)
{
  console.log(i);
  await Promise.resolve();
  go(i+1);
}

go(0);

第二个代码实际上使用了恒定数量的内存。


结论:函数内的任何(可达)await都将中断递归调用。代码如下所示:

const deleteDocsBatch = async (cacheRef, batchSize) => {
  try {
    // get all the cached docs, limit to 30 to avoid potential memory issues
    const snapShot = await cacheRef.limit(batchSize).get();
    if (snapShot.size === 0) { return; }

    const batch = fsDb.batch();
    snapShot.docs.forEach((doc) => {
      batch.delete(doc.ref);
    });

    await batch.commit();
    await deleteDocsBatch(cacheRef, batchSize);
  } catch (e) {
    console.error('error in deleteDocsBatch:' + e);
  }
};

,您唯一需要担心的递归问题是停止条件(例如:是否存在该函数永不结束的情况?)。该代码不会占用您的内存。