web-dev-qa-db-ja.com

非同期/待機関数を並行して呼び出す

私が理解している限りでは、ES7/ES2016では、複数のawaitをコードに入れることは、.then()をpromiseでチェーニングするのと同じように機能します。それで、例えば、我々はこのコードを持っています:

await someCall();
await anotherCall();

anotherCall()は、someCall()が完了したときにのみ呼び出されると正しく理解していますか?それらを並行して呼び出す最もエレガントな方法は何ですか?

Nodeで使用したいのですが、非同期ライブラリを使用したソリューションはあるのでしょうか。

編集:私はこの質問で提供される解決策に満足していない: 非同期ジェネレータでの約束を並行して待っていないためにスローダウン それはジェネレータを使用し、私はより一般的なユースケースについて尋ねている。

259
Victor Marchuk

あなたはPromise.all()で待つことができます:

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

TL; DR

並列関数呼び出しにはPromise.allを使用してください。エラーが発生したときの応答動作が正しくありません。


まず、非同期呼び出しを一度にall実行し、すべてのPromiseオブジェクトを取得します。次に、awaitオブジェクトでPromiseを使用します。この方法では、最初のPromiseが他の非同期呼び出しを解決するのを待っている間、まだ進行中です。全体として、最も遅い非同期呼び出しの間だけ待つことになります。例えば:

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

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

警告:await呼び出しは、最初のawait呼び出しはすべての非同期呼び出しの後にafter発生します。 JohnnyHKのコメントを参照してください。


更新:@ bergi's answer に応じて、この回答はエラー処理のタイミングが異なります。NOTエラーが発生するとエラーをスローしますが、すべてのプロミスが実行された後です。結果を@jonnyのヒントと比較します:[result1, result2] = Promise.all([async1(), async2()])、次のコードスニペットを確認します

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

更新:

元の答えでは、約束の拒否を正しく処理することが困難(場合によっては不可能)になります。正しい解決策はPromise.allを使うことです。

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

元の答え:

どちらか一方を待つ前に、必ず両方の関数を呼び出すようにしてください。

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

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

私は a Gist を作成して、約束を解決するいくつかの異なる方法をテストし、結果を出しています。うまくいくオプションを見ることは役に立つかもしれません。

6
SkarXa

Promise.all()を使わずに並行して行う別の方法があります。

まず、数字を印刷するための2つの関数があります。

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

これは順次です。

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

これは平行です。

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
5
user2883596
    // 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
    */

P1、p2、およびp3の設定は厳密に並行して実行されませんが、実行を保留せず、キャッチでコンテキストエラーをトラップできます。

1
Thrunobulax