hiroakirigit
@hiroakirigit

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

JavaScriptの非同期処理の複数処理を直列で実行する際の書き方の違いがわかりません。

解決したいこと

JavaScriptの非同期処理に関する質問です。複数処理を直列で実行する際の正解のコードと間違いのコードに関する違いがわかりません。

「promiseFactory()という関数に引数0を渡して実行したpromiseFactory(0)」

「トップレベルで変数countを0と定義して、promiseFactory()を実行して定数instanceに格納した場合」もどちらも関数内のreturn new PromiseからPromiseインスタンスを返しているように思えてしまいます。どう分解すればわかるのか、そもそもの挙動の違いなど教えていただけますと幸いです。
※正解・間違いの中のコードで★をつけていますので参照ください。

正解のコード

function promiseFactory(count) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                count++;
                console.log(`${count}回目のコールです。時刻:[${new Date().toTimeString()}]`);
                if (count === 3) { // 3回目のコールでエラー
                    reject(count);
                } else {
                    resolve(count);
                }
            }, 1000);
        });
    }
    promiseFactory(0) // ★★★★★★★★★
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .catch(errorCount => {
            console.error(`エラーに飛びました。現在のカウントは ${errorCount} です。`);
        }).finally(() => {
            console.log("処理を終了します。");
        });
    // > 1回目のコールです。時刻:[16:23:19 GMT+0900 (日本標準時)]
    // > 2回目のコールです。時刻:[16:23:20 GMT+0900 (日本標準時)]
    // > 3回目のコールです。時刻:[16:23:21 GMT+0900 (日本標準時)]
    // > エラーに飛びました。現在のカウントは3です。
    // > 処理を終了します。

間違いのコード

let count = 0;
    function promiseFactory() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                count++;
                console.log(`${count}回目のコールです。時刻:[${new Date().toTimeString()}]`);
                if (count === 3) { // 3回目のコールでエラー
                    reject(count);
                } else {
                    resolve(count);
                }
            }, 1000);
        });
    }
    const instance = promiseFactory(); //★★★★★★★★★★
    instance
        .then(() => { return instance; })
        .then(() => { return instance; })
        .then(() => { return instance; })
        .catch(errorCount => {
            console.error(`エラーに飛びました。現在のカウントは ${errorCount} です。`);
        }).finally(() => {
            console.log("処理を終了します。");
        });
    // > 1回目のコールです。時刻:[16:50:00 GMT+0900 (日本標準時)]
    // > 処理を終了します。

自分で試したこと

console.log(typeof promiseFactory(0)) // > Object
console.log(typeof instance) // > Object 

だったので余計わからなくなりました。

(補足)
参考書には間違えている理由として、promiseFactory()(間違いとしてあげたコード中のの★部分)の記述によって、promiseFactory関数内部のコードが実行され、Promiseのコールバック関数(reject, resolve) => {...}が同期的に実行される。
と書いてあるのですが、どうしてそうなるのでしょうか?

0

4Answer

    promiseFactory(0) 
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })

promiseFactoryを実行した際にPromiseがnewされるため,これらのthen()がそれぞれ返すのは毎回別のPromiseインスタンスであることに注意してください.

一度生成したPromiseインスタンスは,1回実行されたのち動作を終了します.
すでにresolveされたPromiseインスタンスを返しても,そのままthen()にスキップされるだけです.

0Like

Comments

  1. @hiroakirigit

    Questioner

    ご回答ありがとうございます。

    2点質問があります。
    ①この場合、promiseFactory()とpromiseFactory(0)では何が違うのでしょうか?

    ②どちらも関数の中では、値がインクリメントされているかと思うのですが、引数を渡すか渡さないかというのは、このPromiseの理解においてあまり重要ではないのでしょうか?

コメントへの返信が長いのでこちらに書きます.

とりあえず,今回promiseFactoryの引数の違いはPromiseへの挙動とは直接関係がありません.

今回のサンプルソースがしていることは,3回まで処理を行えるように,countの値を管理することです.
Promiseは何らかの値をresolveすると,それがthenコールバックの引数に渡されます(awaitすると返値のように取り出すことができます).
デバッグすれば各then時にcountの値が変わっていることがわかると思います.

このとき,変数名は同じでも,まったく別の変数として扱われていることには注意してください.
すなわち

    promiseFactory(0) 
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })
        .then(count => { return promiseFactory(count); })

    promiseFactory(0) 
        .then(c1 => { return promiseFactory(c1); })
        .then(c2 => { return promiseFactory(c2); })
        .then(c3 => { return promiseFactory(c3); })
        .then(c4 => { return promiseFactory(c4); })

と等価です.

特に非同期処理を用いる場合については注意すべき点として,関数外のスコープにある変数が予期しないタイミングで操作されるようなコーディングは悪習とされます.
promiseFactoryへ引数を渡しているのは,resolveを介して実行回数をやり取りすることで,非同期関数がグローバル変数を直接操作することなく回数を把握するためです.

0Like

@Vercleneさんの回答により、少し紐解けたような気がしました。

自分で再度公式のリファレンスとテキストを読んでみました。

再認識

  • then()もしくはcatch()の中で登録されたコールバック関数は、Promiseオブジェクトを返していること
  • 当たり前だが処理が続くにつれてthen()、catch()で返されるPromiseオブジェクトは変わっていくこと
  • ★の部分で既に関数自体が実行されること
  • ★の部分で実行された初回のpromiseFactory()関数がそのまま、.then()に登録されたコールバック関数で返されているということ
    const instance = promiseFactory(); //★★★★★★★★★★
    instance
        .then(() => { return instance; })
        .then(() => { return instance; })
        .then(() => { return instance; })

がわかりました。

理解の深まり

私なりの詳細を下記に記したいと思います。

    const instance = promiseFactory(); // -------①
    instance
        .then(() => { return instance; }) // -------②
        .then(() => { return instance; }) // -------③
        .then(() => { return instance; }) // -------④
        .catch(errorCount => {
            console.error(`エラーに飛びました。現在のカウントは ${errorCount} です。`);
        }).finally(() => {
            console.log("処理を終了します。");
        });

    // ⑤ ==================
    // > 1回目のコールです。時刻:[16:50:00 GMT+0900 (日本標準時)] 
    // > 処理を終了します。
    // =====================
  1. ①ですでにpromiseFactory()を実行済みで、コンソールに表示される一行目はこのタイミングで実行されることによる表示。※実際に①だけ残して、開発ツールもしくはnodeコマンドを実行すると、同じ結果がコンソールに表示される。
  2. ②③④で返されている定数instanceは①で実行されたものと変わらず、再度promiseFactory関数が実行されるわけではないので、同関数の内部にあるcountのインクリメントもconsole.log(...)も実行はされない。
  3. ⑤の出力結果は、resolve、rejectの条件処理とは関係ない。
    この理由として②③④が何も実行されない処理のためコメントアウトすると、
    const instance = promiseFactory(); // -------①
    instance
        .finally(() => {
            console.log("処理を終了します。");
        });

となり、初回のpromiseFactory()の実行によるPromiseオブジェクトの戻り値を格納した定数に、.finally()がつながっているだけの処理と変わらないと感じました。

3.に関しては解釈が間違っているかもしれないので、何かありましたらご指摘いただきたいです。

@Verclene
ご回答ありがとうございます。
おかげさまで、公式ドキュメントを自分なりに読み直して少し理解できたと思います。まだまだ理解不足なのは否めないですが。。。
一旦これでクローズとさせていただければと思います。

0Like

直接は関係ありませんが、アイコン画像は変更したほうがよろしいかと存じます。

0Like

Comments

  1. @hiroakirigit

    Questioner

    ご指摘ありがとうございます。変えました。

Your answer might help someone💌