Wait promise inside for loop

不打扰是莪最后的温柔 提交于 2020-01-09 05:23:07

问题


let currentProduct;

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

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

I'm using bluebird, the methods getAll and update return promises. How can I say "Wait until the two promises return, then update the currentProduct value"? I'm quite new to JS...


回答1:


This will be straightforward if you can use 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);
    */
}

Or if you can only use plain promises, you can loop through all your products, and put each promise in the .then of the last loop. In that way, it will only advance to the next when the previous has resolved (even though it will have iterated the whole loop first):

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

In the second snippet, the loop just sets up the entire chain, but doesn't execute the code inside the .then immediately. Your getAll functions won't run until each prior one has resolved in turn (which is what you want).




回答2:


Here is how I'd do it:

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

No need to manually chain promises or iterate arrays by index :)




回答3:


You may want to keep track of what products you've processed because when one fails you have no idea how many succeeded and you don't know what to correct (if roll back) or retry.

The async "loop" could be a recursive function:

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

Without async you can use recursion or reduce:

//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

Here is how you'd call 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
  )
)


来源:https://stackoverflow.com/questions/48014050/wait-promise-inside-for-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!