我在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
好的,所以利用这样一个事实,当你通过.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);
}
};
你应该担心的唯一递归问题是停止条件(例如:是否存在该函数永远不会结束的情况?)。那段代码不会吃掉你的记忆。