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


433

私が理解している限り、ES7 / ES2016では、複数awaitのをコードに入れることは、promiseのチェーン.then()と同様に機能します。つまり、それらは並列ではなく、次々に実行されます。したがって、たとえば、次のコードがあります。

await someCall();
await anotherCall();

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

Nodeで使用したいので、非同期ライブラリを使用した解決策はありますか?

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


1
@adeneo不正解です。JavaScriptが独自のコンテキスト内で並行して実行されることはありません。
Blindman67 2016

5
@ Blindman67-これは、少なくともOPが意味する方法で2つの非同期操作が同時に実行されていますが、この場合ではなく、シリアルで実行されるというものawaitで、最初の関数が完了するまで待機します完全に2番目を実行する前に。
adeneo 2016

3
@ Blindman67-シングルスレッドですが、その制限は非同期メソッドには適用されません。非同期メソッドは同時に実行でき、完了時に応答を返します。つまり、OPが「parallell」で意味するものです。
adeneo

7
@ Blindman67-OPが何を要求しているかはかなり明確だと思います。非同期/待機パターンを使用すると、たとえ非同期であっても関数がシリアルで実行されるため、最初の関数が完全に終了してから2番目の関数が呼び出されます。OPは両方の関数を並列で呼び出す方法を尋ねると、それらは明らかに非同期であるため、それらを同時に実行すること、つまり、並列で実行すること、たとえば、2つのajaxリクエストを同時に実行することは、ほとんどの非同期メソッドと同様に、JavaScriptではまったく問題ありません。 、あなたが指摘したように、ネイティブコードを実行し、より多くのスレッドを使用します。
adeneo

3
@Bergiこれはリンクされた質問の複製ではありません—これは特に非同期/待機構文とネイティブPromisesに関するものです。リンクされた質問は、ジェネレーターと歩留まりを備えたbluebirdライブラリーに関するものです。概念的にはおそらく似ていますが、実装されていません。
Iest

回答:


702

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

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

結果を保存するには:

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

Promise.allはすぐに失敗することに注意してください。つまり、提供されたpromiseの1つが拒​​否されるとすぐに、全体が拒否されます。

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

代わりに、すべての約束が満たされるか拒否されるのを待つ場合は、を使用できますPromise.allSettled。Internet Explorerはこのメソッドをネイティブにサポートしていないことに注意してください。

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]


78
クリーンですが、Promise.allの高速フェイル動作に注意してください。関数のいずれかがエラーをスローした場合、Promise.allは拒否します
NoNameProvided

11
あなたが見る、非同期/のawaitとうまく部分的な結果を扱うことができるstackoverflow.com/a/42158854/2019689を
NoNameProvidedを

131
プロチップ:Promise.all(からの結果の任意の数を初期化するために使用するアレイ非構造)、等:[result1, result2] = Promise.all([async1(), async2()]);
ジョニー

10
@jonnyこれはフェイルファストの対象ですか?また、まだ必要= await Promise.allですか?
theUtherSide 2018

5
@theUtherSideあなたは完全に正しいです-私は待機を含めることを怠りました。
ジョニー2018

114

TL; DR

Promise.all並列関数呼び出しに使用します。エラーが発生した場合の応答動作は正しくありません。


まず、すべての非同期呼び出しを一度に実行、すべてのPromiseオブジェクトを取得します。次に、オブジェクトに使用awaitPromiseます。このように、最初の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呼び出しがすべての非同期呼び出しの後に行われる限り、呼び出しが同じ行にあるか別の行にあるかは問題ではありません。JohnnyHKのコメントを参照してください。


更新:この回答では、@ bergiの回答に従ってエラー処理のタイミングが異なります。エラーが発生しても、すべてのpromiseが実行された後、エラーはスローされませ。結果を@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();
})();


11
このPromise.allよりも私には非常に良くオプションのように見える-とあなたも行うことができます割り当て構造化代入して[someResult, anotherResult] = [await someResult, await anotherResult]、あなたが変更した場合constにはlet
jawj 2017

28
しかし、これはまだawaitステートメントをシリアルに実行しますよね?つまり、最初の問題がawait解決するまで実行が一時停止し、次に2番目の問題に移ります。Promise.all並列実行します。
-Andru

8
@Havenありがとうございます。これは受け入れられる答えになるはずです。
ステファンD

87
両方の待機が同じ行で行われるという事実は無関係であるため、この回答は誤解を招くものです。重要なのは、2つの非同期呼び出しがいずれかが待機する前に行われることです。
JohnnyHK、2017年

15
@Havenこのソリューションはと同じではありませんPromise.all。各リクエストがネットワークコールである場合、await someResultawait anotherResult開始される前に解決する必要があります。逆に、どちらか一方が解決される前にPromise.all、2つのawait呼び出しを開始できます。
ベンワインディング

89

更新:

元の回答では、約束の拒否を正しく処理することが困難(場合によっては不可能)になります。正しい解決策は以下を使用すること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;

1
@JeffFischer私はうまくいけばそれを明確にするコメントを追加しました。
ジョナサンポッター

9
これは確かに最も純粋な答えだと思います
Gershom

1
この答えはヘイブンの答えよりもはるかに明確です。関数呼び出しがpromiseオブジェクトを返し、awaitそれを実際の値に解決することは明らかです。
user1032613 2018

3
これは一見すると機能しているように見えますが、未処理の拒否に関する恐ろしい問題あります。これは使用しないでください。
ベルギ

1
@Bergiその通り、指摘してくれてありがとう!私はより良い解決策で答えを更新しました。
ジョナサンポッター

24

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

10

これは、Promise.allSettled()で実現できます。これはPromise.all()、フェイルファスト動作に似ていますが、フェイルファスト動作はありません。

async function failure() {
    throw "Failure!";
}

async function success() {
    return "Success!";
}

const [failureResult, successResult] = await Promise.allSettled([failure(), success()]);

console.log(failureResult); // {status: "rejected", reason: "Failure!"}
console.log(successResult); // {status: "fulfilled", value: "Success!"}

:これは最先端の機能であり、ブラウザのサポートが制限されているため、この機能にはポリフィルを含めることを強くお勧めします。


7

私は約束を解決するいくつかの異なる方法をテストし、結果を示す要点を作成しまし。機能するオプションを確認すると役立つ場合があります。


要点のテスト4と6は期待される結果を返しました。オプションの違いを説明するNoNameProvidedによるstackoverflow.com/a/42158854/5683904を参照してください。
18年

1
    // 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を設定しても厳密に並行して実行されるわけではありませんが、実行を妨げることはなく、catchでコンテキストエラーをトラップできます。


2
Stack Overflowへようこそ。あなたのコードは質問に対する答えを提供するかもしれませんが、周りにコンテキストを追加してください。そうすることで、他の人がそれが何をするのか、なぜそこにあるのかについていくつかのアイデアを持っています。
Theo

1

私の場合、並行して実行したいタスクがいくつかありますが、それらのタスクの結果を使用して別のことをする必要があります。

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

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');

そして出力:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done

動的な作成に
最適

1

await Promise.all([someCall()、anotherCall()]); すでに述べたように、スレッドフェンスとして機能します(CUDAとして並列コードで非常に一般的)。したがって、その中のすべてのpromiseは互いにブロックせずに実行できますが、ALLが解決されるまで実行は続行されません。

共有する価値のある別のアプローチは、タスクがAPI呼び出し、I / O操作などの限られたリソースの使用に直接リンクされている場合に通常望ましい同時実行の量を簡単に制御できるNode.js非同期です。等

// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
  console.log('Hello ' + task.name);
  callback();
}, 2);

// assign a callback
q.drain = function() {
  console.log('All items have been processed');
};

// add some items to the queue
q.push({name: 'foo'}, function(err) {
  console.log('Finished processing foo');
});

q.push({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
  console.log('Finished processing item');
});

// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

Medium記事の講師へのクレジット(詳細はこちら


-5

私は投票します:

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

関数を呼び出す瞬間に注意してください。予期しない結果を引き起こす可能性があります。

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

ただし、次の場合は常に新しいユーザーの作成要求がトリガーされます

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

条件テストの外側/前で関数を宣言し、それらを呼び出したからです。それらをelseブロックでラップしてみてください。
Haven

@Haven:関数を呼び出すときと待つときを分けると、たとえば非同期HTTPリクエストなど、予期しない結果が生じる可能性があることを意味します。
Hoang Le Anh Tu

-6

ヘルパー関数waitAllを作成しました。現時点では、nodejsでのみ機能し、ブラウザクロムでは機能しません

    //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());

3
いいえ、ここでは並列化はまったく行われていません。forループは順次各約束を待って、配列に結果が追加されます。
SzczepanHołyszewski2018

これは人々のために働いていないようだと私は理解しています。だから私はnode.jsとブラウザでテストしました。テストはnode.js(v10、v11)、firefoxでパスされ、ブラウザクロムでは機能しません。テストケースであるgist.github.com/fredyang/ea736a7b8293edf7a1a25c39c7d2fbbf
フレッド・ヤン

2
私はこれを信じることを拒否します。forループのさまざまな反復を自動的に並列化できるという標準には何もありません。これはjavascriptの動作方法ではありません。ループコードの記述方法は、次のことを意味します。「1つのアイテムを待つ(await expr)、THENは結果をtempにプッシュし、THENは次のアイテムを取る(forループの次の反復)。各アイテムの「待機」は完全にループの1回の反復に限定されます。テストによって並列化が示されている場合、トランスパイラーが標準的でないことを行っているか、バグがまったくないためです
SzczepanHołyszewskiFeb

@SzczepanHołyszewskiテストケースを実行せずに不正解を見つけたという自信があるので、名前を変更するためのリファクタリングと追加のコメントを作成するように促されました。すべてのコードはプレーンな古いES6であり、トランスパイルする必要はありません。
フレッドヤン

なぜこれが非常に反対投票されているのかわからない。これは、@ user2883596が出した答えと本質的に同じです。
ジョナサンスディアマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.