简体   繁体   English

不使用 async/await 的 reduce() function 中的异步性

[英]Asyncronicity in a reduce() function WITHOUT using async/await

I am patching the exec() function to allow subpopulating in Mongoose, which is why I am not able to use async/await here -- my function will be chained off a db call, so there is no opportunity to call await on it, and within the submodule itself, there I can't add async/await outside of an async function itself.我正在修补 exec() function 以允许在 ZCCADCDEDB567ABAE643E15DCF0974E503Z 中进行子填充,这就是为什么我无法在此处使用async/await的原因——我的 function 没有机会在链上取消等待它在子模块本身中,我无法在异步 function 本身之外添加async/await

With that out of the way, let's look at what I'm trying to do.有了这个,让我们看看我想要做什么。 I have two separate arrays ( matchingMealPlanFoods and matchingMealPlanRecipeFoods ) full of IDs that I need to populate.我有两个单独的 arrays ( matchingMealPlanFoodsmatchingMealPlanRecipeFoods ),里面装满了我需要填充的 ID。 Both of them reside on the same array, foods .它们都驻留在同一个数组中,即foods They each require a db call with aggregation, and the problem in my current scenario is that only one of the arrays populates because they are happening asynchronously.它们每个都需要一个带有聚合的数据库调用,而我当前场景中的问题是只有一个 arrays 填充,因为它们是异步发生的。

What I am trying to do now is use the reduce function to return the updated foods array to the next run of reduce so that when the final result is returned, I can replace the entire foods array once on my doc .我现在要做的是使用reduce function 将更新后的 foods 数组返回到下一次reduce运行,这样当返回最终结果时,我可以在我的doc上替换整个foods数组。 The problem of course is that my aggregate/exec has not yet returned a value by the time the reduce function goes into its next run.问题当然是,当reduce function 进入下一次运行时,我的aggregate/exec还没有返回值。 Is there a way I can achieve this without async/await here?有没有一种方法可以在没有async/await的情况下实现这一点? I'm including the high-level structure here so you can see what needs to happen, and why using .then() is probably not viable.我在这里包含了高级结构,这样您就可以看到需要发生什么,以及为什么使用.then()可能不可行。

EDIT: Updating code with async suggestion编辑:使用异步建议更新代码

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]);
  }
})()

EDIT: Since it seems it will help, here is the what is calling the execute function I have excerpted above.编辑:因为它似乎会有所帮助,所以这就是我在上面摘录的调用execute function 的内容。

 /**
   * 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();
  };
};

We can use async/await just fine here, as long as we remember that async is the same as "returning a Promise" and await is the same as "resolving a Promise's.then or.catch".我们可以在这里使用 async/await 就好了,只要我们记住async和“返回一个 Promise”是一样的, await和“resolving a Promise's.then or.catch”是一样的。

So let's turn all those "synchronous but callback-based" calls into awaitables: your outer code has to keep obeying the API contract, but since it's not meant to a return a value, we can safely mark our own version of it as async , and then we can use await in combination with promises around any other callback based function calls in our own code just fine:因此,让我们将所有这些“同步但基于回调”的调用转换为可等待对象:您的外部代码必须继续遵守 API 合约,但由于它并不意味着返回值,我们可以安全地将我们自己的版本标记为async ,然后我们可以在我们自己的代码中将await与任何其他基于回调的 function 调用结合使用,就好了:

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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM