web-dev-qa-db-fra.com

Comment puis-je attendre plusieurs promesses en parallèle sans comportement «fail-fast»?

J'utilise async/await pour déclencher plusieurs appels api en parallèle:

async function foo(arr) {
  const results = await Promise.all(arr.map(v => {
     return doAsyncThing(v)
  }))
  return results
}

Je sais que, contrairement à loops, Promise.alls'exécute en parallèle (c'est-à-dire que la partie de l'attente des résultats est en parallèle).

Mais je le sais aussi :

Promise.all est rejeté si l’un des éléments est rejeté et si Promise.all échoue rapidement: si vous avez quatre promesses qui se résolvent après un délai expiré et l’une rejetée immédiatement, Promise.all rejette immédiatement.

Si je lis ceci, si je Promise.all Avec 5 promesses et que le premier à terminer retourne un reject(), les 4 autres sont effectivement annulées et leur promis resolve() les valeurs sont perdues.

Y a-t-il une troisième voie? Où l'exécution est effectivement parallèle, mais qu'un seul échec ne gâche rien?

42
Brandon

L'utilisation de catch signifie que la promesse est résolue (à moins que vous ne leviez une exception de catch ou que vous rejetiez manuellement la chaîne de promesse), vous n'avez donc pas besoin de renvoyer explicitement une promesse résolue IIUC.

Cela signifie que simplement en traitant les erreurs avec catch, vous pouvez obtenir ce que vous voulez.

Si vous souhaitez standardiser la manière dont les rejets sont gérés, vous pouvez appliquer une fonction de traitement des rejets à toutes les promesses.

async function bar() {
    await new Promise(r=> setTimeout(r, 1000))
      .then(()=> console.log('bar'))
      .then(()=> 'bar result');
}
async function bam() {
    await new Promise((ignore, reject)=> setTimeout(reject, 2000))
      .catch(()=> { console.log('bam errored'); throw 'bam'; });
}
async function bat() {
    await new Promise(r=> setTimeout(r, 3000))
      .then(()=> console.log('bat'))
      .then(()=> 'bat result');
}

function handleRejection(p) {
    return p.catch(err=> ({ error: err }));
}

async function foo(arr) {
  console.log('foo');
  return await Promise.all([bar(), bam(), bat()].map(handleRejection));
}

foo().then(results=> console.log('done', results));
38
Ben

Bien que la technique de la réponse acceptée puisse résoudre votre problème, elle est anti-modèle. Résoudre une promesse avec une erreur n'est pas une bonne pratique et il existe un moyen plus simple de le faire.

Ce que vous voulez faire, c'est en pseudo langage, c'est:

fn task() {
  result-1 = doAsync();
  result-n = doAsync();

  // handle results together
  return handleResults(result-1, ..., result-n)
}

Ceci peut être réalisé simplement avec async/await sans avoir besoin d'utiliser Promise.all. Un exemple de travail:

console.clear();

function wait(ms, data) {
  return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}

/** 
 * This will be runned in series, because 
 * we call a function and immediately wait for it's result, 
 * so this will finish in 1s.
 */
async function series() {
  return {
    result1: await wait(500, 'seriesTask1'),
    result2: await wait(500, 'seriesTask2'),
  }
}

/** 
 * While here we call the functions first,
 * then wait for the result later, so 
 * this will finish in 500ms.
 */
async function parallel() {
  const task1 = wait(500, 'parallelTask1');
  const task2 = wait(500, 'parallelTask2');

  return {
    result1: await task1,
    result2: await task2,
  }
}

async function taskRunner(fn, label) {
  const startTime = performance.now();
  console.log(`Task ${label} starting...`);
  let result = await fn();
  console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}

void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');

Remarque: vous aurez besoin d'un navigateur avec async/await activé pour exécuter cet extrait.

De cette façon, vous pouvez utiliser simplement try/catch pour gérer vos erreurs et renvoyer des résultats partiels dans la fonction parallel.

80
NoNameProvided