非同期関数で複数の待機を処理する方法


8

APIを介してフェッチし、APIを介してDBにデータを書き込み、別のAPIを介してフロントエンドに出力を送信する複数のAPI呼び出しを行う必要があります。

以下のようにawaitを使用して非同期関数を作成しました-

最初の2つは次々に実行する必要がありますが、3つ目は独立して実行でき、最初の2つのフェッチステートメントが完了するのを待つ必要はありません。

let getToken= await fetch(url_for_getToken);
let getTokenData = await getToken.json();

let writeToDB = await fetch(url_for_writeToDB);
let writeToDBData = await writeToDB.json();

let frontEnd = await fetch(url_for_frontEnd);
let frontEndData = await frontEnd.json();

そのような複数のフェッチ文を処理する最良の方法は何ですか?


6
あなたは見て持つことができPromise.allを
Yannick K

2
@YannickKここでPromise.allが必要でしょうか?代わりに.then()を使用できませんでしたか?彼は両方が完了するのを待っているのではなく、どちらか一方に関係なく、最初 2番目、3番目の順番を待っています。
神戸

1
@Kobe私は、この場合の主な問題は、OPは、彼らがじゃないので、サーバとクライアントの呼び出しを分離したいということだと思い依存お互いに-と、彼らはお互いに待っていたならば、それは愚かなパフォーマンスが賢明だろう-しかし、もし何らかのの彼らはあなたが拒否したい失敗します。彼がなしPromise.allでできることは間違いなく正しいですが、この場合Promise.all、特にエラー処理のために、すべてを1回の呼び出しでラップすると、よりクリーンになる(そして将来的に構築しやすくなる)と思います。
Yannick K

@Kobe Promise.allは、適切なエラー処理と、1番目、2番目、3番目のプロミスの完了を待つために不可欠です
Bergi、

1
最も単純な答えは、問題最善を解決し、残念ながら不当downvotedました。@Yasar Abdulllahさん、試してみる価値はあります。
AndreasPizsa

回答:


2

多くの方法がありますが、最も一般的な方法は、非同期コードパスを非同期関数でラップすることです。これにより、非同期の戻り値を自由に組み合わせて組み合わせることができます。あなたの例では、非同期iifeを使用してコードをインライン化することもできます。

await Promise.all([
  (async() => {
    let getToken = await fetch(url_for_getToken);
    let getTokenData = await getToken.json();

    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
  })(),
  (async() => {
    let frontEnd = await fetch(url_for_frontEnd);
    let frontEndData = await frontEnd.json();
  })()
]);

最後に、適切な答えです!:D
ベルギ

1

.then()待つのではなく、を使用できます。

fetch(url_for_getToken)
  .then(getToken => getToken.json())
  .then(async getTokenData => {
    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
    // Carry on
  })

fetch(url_for_frontEnd)
  .then(frontEnd => frontEnd.json())
  .then(frontEndData => {
    // Carry on  
  })

1
これらのプロミスチェーンのエラーを処理することを忘れないでください-またはPromise.all、それらを呼び出し元に返します。
ベルギ

1

生のプロミスではなく、プロミスの「作成者」(=プロミスを返す関数)を使用する方が簡単です。まず、次を定義します。

const fetchJson = (url, opts) => () => fetch(url, opts).then(r => r.json())

そのような「創造者」を返します。ここで、直列および並列チェーン用の2つのユーティリティを示します。これらは、生のプロミスと「作成者」の両方を受け入れます。

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => Promise.all(fns.map(call));

async function series(...fns) {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
}

次に、メインコードは次のように書くことができます:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        fetchJson(url_for_getToken),
        fetchJson(url_for_writeToDB),
    ),
    fetchJson(url_for_frontEnd),
)

専用の「クリエーター」ラッパーが気に入らない場合は、次のように定義できます。 fetchJson通常どおり

const fetchJson = (url, opts) => fetch(url, opts).then(r => r.json())

インライン継続を適切な場所で使用するseriesか、parallel呼び出される:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        () => fetchJson('getToken'),
        () => fetchJson('writeToDB'),
    ),
    () => fetchJson('frontEnd'), // continuation not necessary, but looks nicer
)

アイデアをさらに進めるために、約束ではなく「作成者」を作成seriesしてparallel返すことができます。このようにして、シリアルおよびパラレルプロミスの任意のネストされた「回路」を構築し、順番に結果を取得できます。完全な動作例:

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => () => Promise.all(fns.map(call));

const series = (...fns) => async () => {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
};

//

const request = (x, time) => () => new Promise(resolve => {
    console.log('start', x);
    setTimeout(() => {
        console.log('end', x)
        resolve(x)
    }, time)
});

async function main() {
    let chain = series(
        parallel(
            series(
                request('A1', 500),
                request('A2', 200),
            ),
            series(
                request('B1', 900),
                request('B2', 400),
                request('B3', 400),
            ),
        ),
        parallel(
            request('C1', 800),
            series(
                request('C2', 100),
                request('C3', 100),
            )
        ),
    );

    let results = await chain();

    console.log(JSON.stringify(results))
}

main()
.as-console-wrapper { max-height: 100% !important; top: 0; }


なんで約束を返さないのfetchJson?promiseを別の関数でラップすることのメリットはわかりません。またseries、シリーズの次の関数は通常、前の関数の戻り値を必要とするため、の有用性には疑問があります。
marzelin

@marzelin:プロミスは作成と同時にseries(promiseA, promiseB)開始されるため、開始AB同時に開始さ れます。
georg

1
ああ、そうです。このすべてのセレモニーを使用するだけseriesです。シーケンスではPromise.allSettledなく、の代わりに非同期iifeを使用しparallelます。
marzelin

@marzelin:この構文はわかりやすくなっています(更新を参照)。
georg

1
@KamilKiełczewski:投稿を更新しました...アイデアは、この継続構文により、入れ子になったプロミス「フロー」を簡単に構築できるようになるというものです。
ゲオルク

-2

writeToDB最初に独立したリクエスト()を実行します。await

let writeToDB = fetch(url_for_writeToDB);

let getToken = await fetch(url_for_getToken);
let getTokenData = await getToken.json();

// ...


1
@Bergi質問はエラー処理に関するものではありません-エラー処理(try-catchなど)は重要ですが、別のトピックです
KamilKiełczewski19年

awaitエラー処理が組み込まれています。promiseが拒否されると、例外が発生し、async function呼び出しによって返されるpromise が拒否され、呼び出し元が通知します。awaitキーワードを削除し、Promiseチェーンを個別に実行することを提案する場合は、エラー処理を考慮せずに削除してはなりません。少なくともそれを述べていない(または拒否された発信者の元の動作を保持している)回答は悪い回答です。
ベルギ

1
この回答はOPの問題を解決し、これまでに提示されたすべての回答の中で最も単純なものでもあります。なぜこれが反対投票されたのか知りたいのですが。OPは「3番目のものは独立して実行でき、最初の2つのフェッチステートメントが完了するのを待つ必要がない」と述べたので、最初に実行しても問題ありません
AndreasPizsa

@AndreasPizsa それは単純すぎる。エラーが処理されないという免責事項が少なくともあれば、私は反対票を投じなかっただろう-それが言及されるとすぐに、誰もがそれが自分のケースに適しているかどうかについて自分の意見を述べることができる-それはOPのためかもしれない。しかし、これを無視すると、すべての最悪のバグにつながり、この答えは役に立たなくなります。
Bergi、

1
@Bergiへのコメントありがとうございます。OPの質問にはエラー処理も含まれていなかったので、簡潔にするために質問の範囲を超えて除外されていると思います。しかし、はい、誰でも自分の意見で投票できます。
AndreasPizsa
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.