在process.nextTick()的递归函数中使用async await

问题描述 投票:0回答:1

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

我尝试了一些不同的东西,但无论我做了什么,async函数都会在所有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

node.js async-await lifecycle event-loop
1个回答
-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);
  }
};

你应该担心的唯一递归问题是停止条件(例如:是否存在该函数永远不会结束的情况?)。那段代码不会吃掉你的记忆。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.