1. ホーム
  2. javascript

[解決済み] JavaScript ES6 promise for loop [重複]について

2022-04-20 19:18:13

質問

for (let i = 0; i < 10; i++) {
    const promise = new Promise((resolve, reject) => {
        const timeout = Math.random() * 1000;
        setTimeout(() => {
            console.log(i);
        }, timeout);
    });

    // TODO: Chain this promise to the previous one (maybe without having it running?)
}

上記を実行すると、以下のようなランダムな出力が得られます。

6
9
4
8
5
1
7
2
3
0

作業は簡単です。各プロミスが他のプロミスの後にのみ実行されることを確認する ( .then() ).

なぜか、やり方が見つからなかった。

ジェネレータ関数( yield )、プロミスを返す単純な関数を試してみましたが、結局いつも同じ問題に帰着します。 ループが同期である .

非同期 私なら単純に async.series() .

どのように解決するのですか?

どのように解決するのですか?

あなたの質問ですでに示唆されているように、あなたのコードはすべてのプロミスを同期的に作成します。代わりに、それらは直前のものが解決される時にのみ作成されるべきです。

次に new Promise を呼び出して解決する必要があります。 resolve (または reject ). これは、タイマーが切れたときに実行する必要があります。これは、すべての then のコールバックは、そのプロミスで持っているはずです。そして、そのような then コールバック(または await ) は、チェーンを実装するために必要です。

これらの材料を用いて、この非同期チェインを実行する方法はいくつかあります。

  1. を使用すると for 即座に解決されるプロミスで始まるループ

  2. Array#reduce で始まり、すぐに解決するプロミス

  3. 解決コールバックとして自分自身を渡す関数を持つ

  4. ECMAScript2017 の async / await シンタックス

  5. ECMAScript2020 の for await...of 構文

しかし、最初にとても便利な汎用的な機能を紹介しよう。

プロミシング setTimeout

使用方法 setTimeout でも良いのですが、実際にはタイマーが切れたときに解決するプロミスが必要です。そこで、そのような関数を作ってみましょう。 プロミス化 をプロミス化します。 setTimeout . これはコードの可読性を向上させ、上記のすべてのオプションに使用することができます。

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

以下、各オプションのスニペットとコメントをご覧ください。

1. を使って for

あなた できる を使用します。 for しかし、それがすべての約束を同期的に作成しないことを確認する必要があります。その代わりに、すぐに解決する最初の約束を作り、そして前の約束が解決するにつれて新しい約束を連鎖させます。

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

for (let i = 0, p = Promise.resolve(); i < 10; i++) {
    p = p.then(() => delay(Math.random() * 1000))
         .then(() => console.log(i));
}

つまり、このコードでは then を呼び出します。変数 p は、その連鎖を見失わないようにし、次のループの反復で同じ連鎖を続けられるようにするためだけのものです。コールバックは同期ループが完了した後に実行を開始する。

重要なのは then -コールバック を返します。 という約束は delay() を作成します。これにより、非同期な連鎖が保証されます。

2. と reduce

これは、先ほどの戦略をより機能的にしただけのものです。実行したいチェーンと同じ長さの配列を作成し、すぐに解決できるプロミスで開始します。

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

[...Array(10)].reduce( (p, _, i) => 
    p.then(() => delay(Math.random() * 1000))
     .then(() => console.log(i))
, Promise.resolve() );

これは、おそらく実際に使用するときに便利です。 ある プロミスで使用されるデータを持つ配列。

3. 解決コールバックとして自分自身を渡す関数を使用する場合

ここでは、関数を作成し、すぐに呼び出します。この関数は最初の約束を同期的に作成します.それが解決されると、関数が再び呼び出されます。

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

(function loop(i) {
    if (i >= 10) return; // all done
    delay(Math.random() * 1000).then(() => {
        console.log(i);
        loop(i+1);
    });
})(0);

という名前の関数が作成されます。 loop そして、コードの最後のほうで、引数 0 ですぐに呼び出されるのがわかります。 これはカウンタであり i 引数で指定します。この関数は、カウンタがまだ10以下であれば新しいプロミスを作成し、そうでなければ連鎖を停止します。

いつ delay() が解決されると、それがきっかけで then コールバックは、この関数を再度呼び出します。

4. と async / await

最新のJSエンジン はこの構文をサポートしています。 :

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

(async function loop() {
    for (let i = 0; i < 10; i++) {
        await delay(Math.random() * 1000);
        console.log(i);
    }
})();

というように、奇妙に見えるかもしれません。 と思われる は、プロミスが同期的に作成されるように見えますが、実際には async 機能 を返します。 を実行したとき、最初の await . 待ち受けが解決されるたびに,関数の実行コンテキストが復元され,その後に続く await そして、次のループに出会うまで、ループが終了するまで続けられる。

5. と for await...of

EcmaScript 2020 では for await...of は、最新のJavaScriptエンジンに採用されました。この場合、コードを減らすことはできませんが、乱数区間の連鎖の定義と実際の反復を分離することができます。

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

async function * randomDelays(count, max) {
    for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i);
}

(async function loop() {
    for await (let i of randomDelays(10, 1000)) console.log(i);
})();