web-dev-qa-db-fra.com

Appeler des fonctions asynchrones / wait en parallèle

Autant que je sache, dans ES7/ES2016, l'insertion de plusieurs await dans le code fonctionnera de manière similaire à l'enchaînement de .then() avec des promesses, ce qui signifie qu'ils s'exécuteront les uns après les autres plutôt que de façon parallèle. Donc, par exemple, nous avons ce code:

await someCall();
await anotherCall();

Dois-je bien comprendre que anotherCall() ne sera appelé que lorsque someCall() sera terminé? Quelle est la manière la plus élégante de les appeler en parallèle?

Je veux l'utiliser dans Node, alors peut-être qu'il existe une solution avec une bibliothèque asynchrone?

EDIT: Je ne suis pas satisfait de la solution fournie dans cette question: Ralentissement dû à une attente non parallèle des promesses dans les générateurs asynchrones , car il utilise des générateurs et je demande un cas d'utilisation plus général .

308
Victor Marchuk

Vous pouvez attendre sur Promise.all():

await Promise.all([someCall(), anotherCall()]);

Pour stocker les résultats:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
492
madox2

TL; DR

Utilisez Promise.all pour les appels de fonction parallèles, les comportements de réponse ne sont pas corrects lorsque l'erreur se produit.


Tout d'abord, exécutez tous les appels asynchrones à la fois et obtenez tous les objets Promise. Deuxièmement, utilisez await sur les objets Promise. Ainsi, pendant que vous attendez que le premier Promise soit résolu, les autres appels asynchrones sont toujours en cours. Dans l'ensemble, vous n'attendrez que le plus long appel asynchrone. Par exemple:

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

Exemple JSbin: http://jsbin.com/xerifanima/edit?js,console

Avertissement: Peu importe que les appels await se trouvent sur la même ligne ou sur des lignes différentes, tant que le premier await appel survient après tous les appels asynchrones. Voir le commentaire de JohnnyHK.


Mise à jour: Cette réponse a un timing différent dans la gestion des erreurs selon le réponse de @ bergi , elle le fait PAS élimine l'erreur lorsque l'erreur se produit, mais après l'exécution de toutes les promesses. Je compare le résultat avec le conseil de @ jonny: [result1, result2] = Promise.all([async1(), async2()]), vérifiez l'extrait de code suivant.

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();
96
Haven

Mise à jour:

La réponse initiale rend difficile (et dans certains cas impossible) le traitement correct des rejets de promesses. La solution correcte consiste à utiliser Promise.all:

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Réponse originale:

Assurez-vous d’appeler les deux fonctions avant d’attendre l’une ou l’autre:

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;
79
Jonathan Potter

Il y a une autre manière sans Promise.all () de le faire en parallèle:

Premièrement, nous avons 2 fonctions pour imprimer des numéros:

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

C'est séquentiel:

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

C'est parallèle:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
15
user2883596

J'ai créé n Gist en testant différentes façons de résoudre les promesses, avec des résultats. Il peut être utile de voir les options qui fonctionnent.

6
SkarXa
    // A generic test function that can be configured 
    // with an arbitrary delay and to either resolve or reject
    const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
        console.log(`Done ${ delay }`);
        resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
    }, delay));

    // Our async handler function
    const handler = async () => {
        // Promise 1 runs first, but resolves last
        const p1 = test(10000, true);
        // Promise 2 run second, and also resolves
        const p2 = test(5000, true);
        // Promise 3 runs last, but completes first (with a rejection) 
        // Note the catch to trap the error immediately
        const p3 = test(1000, false).catch(e => console.log(e));
        // Await all in parallel
        const r = await Promise.all([p1, p2, p3]);
        // Display the results
        console.log(r);
    };

    // Run the handler
    handler();
    /*
    Done 1000
    Reject 1000
    Done 5000
    Done 10000
    */

Bien que définir p1, p2 et p3 ne les exécute pas strictement en parallèle, ils ne retardent aucune exécution et vous pouvez intercepter les erreurs contextuelles avec un catch.

1
Thrunobulax