【问题标题】:Wait promise inside for loop等待循环内的承诺
【发布时间】:2022-12-08 04:39:02
【问题描述】:
let currentProduct;

for (let i = 0; i < products.length; i++) { 
    currentProduct = products[i];

    subscription.getAll(products[i]._id)
        .then((subs) => {
            update(subs, currentProduct);
        });
}

我正在使用蓝鸟,方法得到所有更新回报承诺。我怎么能说“等到两个承诺返回,然后更新 currentProduct 值”?我对JS很陌生......

【问题讨论】:

  • 为什么这个问题被标记为异步等待?您要使用此功能吗?

标签: javascript node.js promise async-await bluebird


【解决方案1】:

如果您可以使用async/await,这将很简单:

// Make sure that this code is inside a function declared using
// the `async` keyword.
let currentProduct;

for (let i = 0; i < products.length; i++) { 
    currentProduct = products[i];

    // By using await, the code will halt here until
    // the promise resolves, then it will go to the
    // next iteration...
    await subscription.getAll(products[i]._id)
        .then((subs) => {
            // Make sure to return your promise here...
            return update(subs, currentProduct);
        });

    // You could also avoid the .then by using two awaits:
    /*
    const subs = await subscription.getAll(products[i]._id);
    await update(subs, currentProduct);
    */
}

或者,如果您只能使用简单的承诺,则可以遍历所有产品,并将每个承诺放在最后一个循环的 .then 中。这样,它只会在前一个解决后才前进到下一个(即使它会首先迭代整个循环):

let currentProduct;

let promiseChain = Promise.resolve();
for (let i = 0; i < products.length; i++) { 
    currentProduct = products[i];

    // Note that there is a scoping issue here, since
    // none of the .then code runs till the loop completes,
    // you need to pass the current value of `currentProduct`
    // into the chain manually, to avoid having its value
    // changed before the .then code accesses it.

    const makeNextPromise = (currentProduct) => () => {
        // Make sure to return your promise here.
        return subscription.getAll(products[i]._id)
            .then((subs) => {
                // Make sure to return your promise here.
                return update(subs, currentProduct);
            });
    }

    // Note that we pass the value of `currentProduct` into the
    // function to avoid it changing as the loop iterates.
    promiseChain = promiseChain.then(makeNextPromise(currentProduct))
}

在第二个 sn-p 中,循环只是设置了整个链,但不会立即执行 .then 中的代码。您的 getAll 函数将不会运行,直到每个先前的函数依次解析(这就是您想要的)。

【讨论】:

  • 如果你使用await,你也可以使用它来代替then调用
  • @Bergi 你是对的。您可以使用 await 获取 getAll 的结果,然后在下一行使用另一个 await 将其传递给 update。但是那里的东西仍然有效,而且众所周知,我会混合搭配我的等待。我会说这取决于 OP 自己喜欢哪种风格。
  • @Jumpa 我已经编辑了帖子以包含一对等待的示例,请参阅第一个 sn-p 中的评论部分。
  • 承诺链使用递归最简单,使用 reduce 则不那么简单(请参阅我的回答)。该答案还可以解决或拒绝某些理智的事情,尤其是在因为您需要知道它走了多远而被拒绝时。
  • @neustart47 由于每个“then”链接最后一个,只需在循环完成后将其添加到承诺链的 .then 中。例如,循环后: promiseChain.then(() =&gt; {/* do your thing */})
【解决方案2】:

这是我的做法:

for (let product of products) { 
  let subs = await subscription.getAll(product._id);
  await update(subs, product);
}

无需手动链接承诺或按索引迭代数组:)

【讨论】:

  • 我正在尝试您的代码,我认为它更优雅。无论如何,您在 for 中缺少产品的“let”。我注意到,因为我收到了 UnhandledPromiseRejectionWarning 消息...您能否编辑您的代码并添加一些内容来处理 promise 拒绝?提前谢谢了。编辑:没关系我应该使用 try/catch ...
【解决方案3】:

您可能希望跟踪已处理的产品,因为当一个产品失败时,您不知道有多少成功,也不知道要更正(如果回滚)或重试什么。

异步“循环”可以是一个递归函数:

const updateProducts = /* add async */async (products,processed=[]) => {
  try{
    if(products.length===0){
      return processed;
    }
    const subs = await subscription.getAll(products[0]._id)
    await update(subs, product);
    processed.push(product[0]._id);  
  }catch(err){
    throw [err,processed];
  }
  return await updateProducts(products.slice(1),processed);
}

如果没有异步,您可以使用递归或减少:

//using reduce
const updateProducts = (products) => {
  //keep track of processed id's
  const processed = [];
  return products.reduce(
    (acc,product)=>
      acc
      .then(_=>subscription.getAll(product._id))
      .then(subs=>update(subs, product))
      //add product id to processed product ids
      .then(_=>processed.push(product._id)),
    Promise.resolve()
  )
  //resolve with processed product id's
  .then(_=>processed)
  //when rejecting include the processed items
  .catch(err=>Promise.reject([err,processed]));
}

//using recursion
const updateProducts = (products,processed=[]) =>
  (products.length!==0)
    ? subscription.getAll(products[0]._id)
      .then(subs=>update(subs, product))
      //add product id to processed
      .then(_=>processed.push(products[0]._id))
      //reject with error and id's of processed products
      .catch(err=>Promise.reject([err,processed]))
      .then(_=>updateProducts(products.slice(1),processed))
    : processed//resolve with array of processed product ids

以下是调用 updateProducts 的方式:

updateProducts(products)
.then(processed=>console.log("Following products are updated.",processed))
.catch(([err,processed])=>
  console.error(
    "something went wrong:",err,
    "following were processed until something went wrong:",
    processed
  )
)

【讨论】:

    猜你喜欢
    • 2017-12-16
    • 2019-04-26
    • 2015-11-13
    • 2020-08-16
    • 2016-04-13
    • 1970-01-01
    • 2022-01-03
    • 2017-11-01
    • 1970-01-01
    相关资源
    最近更新 更多