web-dev-qa-db-fra.com

Attendez la promesse à l'intérieur de la boucle

let currentProduct;

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

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

J'utilise bluebird, les méthodes getAll et pdate renvoient des promesses. Comment puis-je dire "Attendez que les deux promesses reviennent, puis mettez à jour la valeur actuelle du produit"? Je suis assez nouveau pour JS ...

7
Jumpa

Ce sera simple si vous pouvez utiliser 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);
    */
}

Ou si vous ne pouvez utiliser que des promesses simples, vous pouvez parcourir tous vos produits et mettre chaque promesse dans le .then de la dernière boucle. De cette façon, elle ne passera à la suivante que lorsque la précédente aura été résolue (même si elle aura d'abord itéré toute la boucle):

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

Dans le deuxième extrait, la boucle configure simplement la chaîne entière, mais n'exécute pas le code à l'intérieur du .then immédiatement. Vos fonctions getAll ne fonctionneront pas tant que chacune d'elles n'aura pas été résolue à son tour (ce que vous voulez).

13
CRice

Voici comment je le ferais:

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

Pas besoin de chaîner manuellement les promesses ou d'itérer les tableaux par index :)

7
Benjamin Gruenbaum

Vous voudrez peut-être garder une trace des produits que vous avez traités, car quand un échoue, vous ne savez pas combien ont réussi et vous ne savez pas quoi corriger (en cas de restauration) ou réessayer.

La "boucle" asynchrone pourrait être une fonction récursive:

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

Sans async, vous pouvez utiliser la récursivité ou réduire:

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

Voici comment vous appelleriez 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
  )
)
2
HMR