web-dev-qa-db-ja.com

「フェイルファースト」動作をせずに複数のプロミスを並行して待機するにはどうすればよいですか?

async/awaitを使用して、いくつかのapi呼び出しを並行して実行しています。

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

loopsとは異なり、Promise.allin-parallelsを実行 (つまり、結果を待つ部分は並列になっています)とは異なります。

しかし 私も知っている

要素の1つが拒​​否され、Promise.allが高速で失敗した場合、Promise.allは拒否されます。タイムアウト後に解決する4つのpromiseがあり、1つがすぐに拒否される場合、Promise.allはすぐに拒否します。

私がこれを読んだとき、私がPromise.allを5つ約束し、最初に終了したものがreject()を返す場合、他の4つは事実上キャンセルされ、約束されたresolve()値は失われます。

第三の方法はありますか?実行は事実上並列であるが、単一の障害で全体が損なわれることはありませんか?

42
Brandon

catchを使用すると、_catchから例外をスローするか、手動でプロミスチェーンを拒否しない限り、プロミスが解決されるため、解決済みプロミスIIUCを明示的に返す必要はありません。

つまり、catchでエラーを処理するだけで、目的を達成できます。

拒否の処理方法を標準化する場合は、すべてのプロミスに拒否処理機能を適用できます。

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

受け入れられた答えのテクニックはあなたの問題を解決できますが、それはアンチパターンです。約束をエラーで解決することは良い習慣ではなく、これを行うよりクリーンな方法があります。

あなたがしたいことは擬似言語です:

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

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

これは、Promise.allを使用せずにasync/awaitで簡単に実現できます。実例:

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

注:このスニペットを実行するには、async/awaitが有効になっているブラウザーが必要です。

このように、単にtry/catchを使用してエラーを処理し、parallel関数内で部分的な結果を返すことができます。

80
NoNameProvided