不使用 async/await 的 reduce() 函数中的异步性
Asyncronicity in a reduce() function WITHOUT using async/await
我正在修补 exec() 函数以允许在 Mongoose 中进行子填充,这就是为什么我不能在这里使用 async/await
的原因——我的函数将被链接到数据库调用中,所以没有有机会在其上调用 await,并且在子模块本身内,我无法在异步函数本身之外添加 async/await
。
说完这些,让我们看看我要做什么。我有两个单独的数组(matchingMealPlanFoods
和 matchingMealPlanRecipeFoods
),里面装满了我需要填充的 ID。它们都位于同一个数组 foods
上。它们每个都需要一个带有聚合的数据库调用,而我当前场景中的问题是只有一个数组填充,因为它们是异步发生的。
我现在想做的是使用 reduce
函数将更新的食物数组 return 到 reduce
的下一个 运行 这样当最后结果是 returned,我可以在我的 doc
上替换整个 foods
数组一次。问题当然是当 reduce
函数进入下一个 运行 时,我的 aggregate/exec
还没有 return 值。有没有办法在没有 async/await
的情况下实现这一目标?我在此处包括高级结构,以便您了解需要发生什么,以及为什么使用 .then()
可能不可行。
编辑:使用异步建议更新代码
function execute(model, docs, options, lean, cb) {
options = formatOptions(options);
let resolvedCount = 0;
let error = false;
(async () => {
for (let doc of docs) {
let newFoodsArray = [...doc.foods];
for (let option of options) {
const path = option.path.split(".");
// ... various things happen here to prep the data
const aggregationOptions = [
// // $match, then $unwind, then $replaceRoot
];
await rootRefModel
.aggregate(aggregationOptions)
.exec((err, refSubDocuments) => {
// more stuff happens
console.log('newFoodsArray', newFoodsArray); // this is to check whether the second iteration is using the updated newFoods Array
const arrToReturn = newFoodsArray.map((food) => {
const newMatchingArray = food[nests[1]].map((matchingFood) => {
//more stuff
return matchingFood;
});
const updatedFood = food;
updatedFood[`${nests[1]}`] = newMatchingArray;
return updatedFood;
});
console.log('arrToReturn', arrToReturn);
newFoodsArray = [...arrToReturn];
});
}
};
console.log('finalNewFoods', newFoodsArray); // this should log after the other two, but it is logging first.
const document = doc.toObject();
document.foods = newFoodsArray;
if (resolvedCount === options.length) cb(null, [document]);
}
})()
编辑:因为它似乎会有所帮助,这里是我在上面摘录的调用 execute
函数的内容。
/**
* This will populate sub refs
* @param {import('mongoose').ModelPopulateOptions[]|
* import('mongoose').ModelPopulateOptions|String[]|String} options
* @returns {Promise}
*/
schema.methods.subPopulate = function (options = null) {
const model = this.constructor;
if (options) {
return new Promise((resolve, reject) => execute(model, [this], options, false, (err, docs) => {
if (err) return reject(err);
return resolve(docs[0]);
}));
}
Promise.resolve();
};
};
我们可以在这里使用 async/await 就好了,只要我们记住 async
与“returning a Promise”相同,而 await
是与“解决 Promise 的 .then 或 .catch”相同。
因此,让我们将所有那些“同步但 callback-based” 调用变成可等待的:您的外部代码必须继续遵守 API 合同,但因为它并不意味着 return一个值,我们可以安全地将我们自己的版本标记为 async
,然后我们可以使用 await
结合我们自己代码中任何其他基于回调的函数调用的承诺:
async function execute(model, docs, options, lean, andThenContinueToThis) {
options = formatOptions(options);
let option, resolvedCount = 0;
for (let doc of docs) {
let newFoodsArray = [...doc.foods];
for (option of options) {
// ...things happen here...
const aggregationOptions = [/*...data...*/];
try {
const refSubDocuments = await new Promise((resolve, reject) => rootRefModel
.aggregate(aggregationOptions)
.exec((err, result) => err ? reject(err) : resolve(result));
// ...do some work based on refSubDocuments...
}
// remember to forward errors and then stop:
catch (err) {
return andThenContinueToThis(err);
}
}
// remember: bind newFoodsArray somewhere so it doesn't get lost next iteration
}
// As our absolutely last action, when all went well, we trigger the call forwarding:
andThenContinueToThis(null, dataToForward);
}
我正在修补 exec() 函数以允许在 Mongoose 中进行子填充,这就是为什么我不能在这里使用 async/await
的原因——我的函数将被链接到数据库调用中,所以没有有机会在其上调用 await,并且在子模块本身内,我无法在异步函数本身之外添加 async/await
。
说完这些,让我们看看我要做什么。我有两个单独的数组(matchingMealPlanFoods
和 matchingMealPlanRecipeFoods
),里面装满了我需要填充的 ID。它们都位于同一个数组 foods
上。它们每个都需要一个带有聚合的数据库调用,而我当前场景中的问题是只有一个数组填充,因为它们是异步发生的。
我现在想做的是使用 reduce
函数将更新的食物数组 return 到 reduce
的下一个 运行 这样当最后结果是 returned,我可以在我的 doc
上替换整个 foods
数组一次。问题当然是当 reduce
函数进入下一个 运行 时,我的 aggregate/exec
还没有 return 值。有没有办法在没有 async/await
的情况下实现这一目标?我在此处包括高级结构,以便您了解需要发生什么,以及为什么使用 .then()
可能不可行。
编辑:使用异步建议更新代码
function execute(model, docs, options, lean, cb) {
options = formatOptions(options);
let resolvedCount = 0;
let error = false;
(async () => {
for (let doc of docs) {
let newFoodsArray = [...doc.foods];
for (let option of options) {
const path = option.path.split(".");
// ... various things happen here to prep the data
const aggregationOptions = [
// // $match, then $unwind, then $replaceRoot
];
await rootRefModel
.aggregate(aggregationOptions)
.exec((err, refSubDocuments) => {
// more stuff happens
console.log('newFoodsArray', newFoodsArray); // this is to check whether the second iteration is using the updated newFoods Array
const arrToReturn = newFoodsArray.map((food) => {
const newMatchingArray = food[nests[1]].map((matchingFood) => {
//more stuff
return matchingFood;
});
const updatedFood = food;
updatedFood[`${nests[1]}`] = newMatchingArray;
return updatedFood;
});
console.log('arrToReturn', arrToReturn);
newFoodsArray = [...arrToReturn];
});
}
};
console.log('finalNewFoods', newFoodsArray); // this should log after the other two, but it is logging first.
const document = doc.toObject();
document.foods = newFoodsArray;
if (resolvedCount === options.length) cb(null, [document]);
}
})()
编辑:因为它似乎会有所帮助,这里是我在上面摘录的调用 execute
函数的内容。
/**
* This will populate sub refs
* @param {import('mongoose').ModelPopulateOptions[]|
* import('mongoose').ModelPopulateOptions|String[]|String} options
* @returns {Promise}
*/
schema.methods.subPopulate = function (options = null) {
const model = this.constructor;
if (options) {
return new Promise((resolve, reject) => execute(model, [this], options, false, (err, docs) => {
if (err) return reject(err);
return resolve(docs[0]);
}));
}
Promise.resolve();
};
};
我们可以在这里使用 async/await 就好了,只要我们记住 async
与“returning a Promise”相同,而 await
是与“解决 Promise 的 .then 或 .catch”相同。
因此,让我们将所有那些“同步但 callback-based” 调用变成可等待的:您的外部代码必须继续遵守 API 合同,但因为它并不意味着 return一个值,我们可以安全地将我们自己的版本标记为 async
,然后我们可以使用 await
结合我们自己代码中任何其他基于回调的函数调用的承诺:
async function execute(model, docs, options, lean, andThenContinueToThis) {
options = formatOptions(options);
let option, resolvedCount = 0;
for (let doc of docs) {
let newFoodsArray = [...doc.foods];
for (option of options) {
// ...things happen here...
const aggregationOptions = [/*...data...*/];
try {
const refSubDocuments = await new Promise((resolve, reject) => rootRefModel
.aggregate(aggregationOptions)
.exec((err, result) => err ? reject(err) : resolve(result));
// ...do some work based on refSubDocuments...
}
// remember to forward errors and then stop:
catch (err) {
return andThenContinueToThis(err);
}
}
// remember: bind newFoodsArray somewhere so it doesn't get lost next iteration
}
// As our absolutely last action, when all went well, we trigger the call forwarding:
andThenContinueToThis(null, dataToForward);
}