synchrone - promise javascript




Appeler des fonctions asynchrones/wait en parallèle (6)

La solution intuitive

function wait(ms, data) {
  console.log('Starting task:', data, ms);
  return new Promise( resolve => setTimeout(resolve, ms, data) );
}

(async function parallel() {

  // step 1 - initiate all promises
  console.log('STARTING')
  let task1 = wait(2000, 'parallelTask1') // PS: see Exception handling below
  let task2 = wait(500, 'parallelTask2')
  let task3 = wait(1000, 'parallelTask3')

  // step 2 - await all promises
  console.log('WAITING')
  task1 = await task1
  task2 = await task2
  task3 = await task3

  // step 3 - all results are 100% ready
  console.log('FINISHED')
  console.log('Result:', task1, task2, task3)

})()

  1. Cela exécutera les promesses une par une, mais instantanément et elles continueront à être exécutées simultanément.
  2. C’est là que nous suspendons l’exécution du code et attendons qu’ils se terminent. Peu importe l'ordre et lequel est résolu en premier. Le code ne continuera pas à l'étape 3 avant que tous les résoudre. Si le premier prend le plus de temps, il n'aura plus à attendre le second, car il l'aurait été au moment où le code y parvient.
  3. C'est fait, les dernières promesses sont résolues et l'exécution du code est terminée en dernier appel.

Avec ES6, vous pouvez même le faire à l’étape 2, après le lancement exécuté.

[task1, task2, task3] = [await task1, await task2, await task3]

PS: Vous pouvez également attendre des calculs internes

let five = getAsyncFive()
let ten = getAsyncTen()

let result = await five * await ten

* Notez que ce n'est pas la même chose que let result = await getAsyncFive() * await getAsyncTen() car cela let result = await getAsyncFive() * await getAsyncTen() pas les tâches asynchrones en parallèle!

gestion des exceptions

Dans l'extrait de .catch(e => e) ci-dessous, le .catch(e => e) une erreur et permet à la chaîne de continuer, permettant ainsi à la promesse de se résoudre plutôt que d'être rejetée . Sans cette catch , le code générerait une exception non gérée et la fonction se fermerait tôt.

const wait = (ms, data) => log(ms,data) || new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => log(ms,data) || new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err-' + e
const l = l => (console.log('Done:', l), l)
const log = (ms, data) => console.log('Started', data, ms)

;(async function parallel() {

  let task1 = reject(500, 'parallelTask1').catch(e).then(l)
  let task2 = wait(2000, 'parallelTask2').catch(e).then(l)
  let task3 = reject(1000, 'parallelTask3').catch(e).then(l)
  
  console.log('WAITING')

  task1 = await task1
  task2 = await task2
  task3 = await task3

  console.log('FINISHED', task1, task2, task3)

})()

Le deuxième extrait n'est pas traité et la fonction échouera.
Vous pouvez également ouvrir Devtools et voir les erreurs dans la sortie de la console.

const wait = (ms, data) => log(ms,data) || new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => log(ms,data) || new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err-' + e
const l = l => (console.log('Done:', l), l)
const log = (ms, data) => console.log('Started', data, ms)

console.log('here1')

;(async function parallel() {

  let task1 = reject(500, 'parallelTask1').then(l) // catch is removed
  let task2 = wait(2000, 'parallelTask2').then(l)
  let task3 = reject(1000, 'parallelTask3').then(l)
  
  console.log('WAITING')

  task1 = await task1
  task2 = await task2
  task3 = await task3

  console.log('FINISHED', task1, task2, task3)

})()

console.log('here2') // Note: "FINISHED" will not run

Autant que je sache, dans ES7 / ES2016, mettre plusieurs codes en await fonctionnera comme si on enchaînait .then() avec des promesses, ce qui signifie qu'ils s'exécuteraient les uns après les autres plutôt que parallèlement. Donc, par exemple, nous avons ce code:

await someCall();
await anotherCall();

Est-ce que je comprends correctement 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.


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

Je crée une fonction d'assistance waitAll, peut-être peut-elle la rendre plus douce. Cela ne fonctionne que dans nodejs pour le moment, pas dans Chrome Chrome.

    //const parallel = async (...items) => {
    const waitAll = async (...items) => {
        //this function does start execution the functions
        //the execution has been started before running this code here
        //instead it collects of the result of execution of the functions

        const temp = [];
        for (const item of items) {
            //this is not
            //temp.push(await item())
            //it does wait for the result in series (not in parallel), but
            //it doesn't affect the parallel execution of those functions
            //because they haven started earlier
            temp.push(await item);
        }
        return temp;
    };

    //the async functions are executed in parallel before passed
    //in the waitAll function

    //const finalResult = await waitAll(someResult(), anotherResult());
    //const finalResult = await parallel(someResult(), anotherResult());
    //or
    const [result1, result2] = await waitAll(someResult(), anotherResult());
    //const [result1, result2] = await parallel(someResult(), anotherResult());

Je vote pour:

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

Soyez conscient du moment où vous appelez des fonctions, cela peut entraîner des résultats inattendus:

// Supposing anotherCall() will trigger a request to create a new User

if (callFirst) {
  await someCall();
} else {
  await Promise.all([someCall(), anotherCall()]); // --> create new User here
}

Mais suivre toujours la requête pour créer un nouvel utilisateur

// Supposing anotherCall() will trigger a request to create a new User

const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User

if (callFirst) {
  await someCall();
} else {
  const finalResult = [await someResult, await anotherResult]
}

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;

TL; DR

Utilisez Promise.all pour les appels de fonction parallèles, les comportements de réponse ne se faisant pas correctement 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 la première Promise les autres appels asynchrones progressent. 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

Mise en garde: Peu importe que les appels en await trouvent sur la même ligne ou sur des lignes différentes, tant que le premier appel en await 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 en fonction de la réponse de @ bergi , elle NE renvoie PAS l'erreur car 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();
})();





babeljs