JavaScript Promiseの実行タイミング Promise.start() とか Promise.execute() 的なもの


Promiseには .start() .execute() が存在しない

Promise objectを作ってtaskを設置したとき、発火方法がわからなかった。

イメージとしては ↓ だったんだけど

const task = new Promise(()=>{ // task});
task.start() // こんなものはない

Promiseにそんなmethodはなかった。使えるのは6つだけ。

  1. Promise.all(promises)
  2. Promise.allSettled(promises)
  3. Promise.race(promises)
  4. Promise.any(promises)
  5. Promise.resolve(value)
  6. Promise.reject(error)

https://javascript.info/promise-api

Promiseは Object を作った瞬間に start() するものだった!!

  1. 関数の中なら、関数が呼ばれると、Promise objectが生成される = 実行される
  2. MAIN関数や <script> の直下なら file loadした瞬間に Promise objectが生成される = 即実行される

発火のコントロール方法

まずPromiseの宣言方法(というか、使い方) は2つ。

  1. Object(変数)として作る
  2. 関数の中に作る(これを個人的には 関数として作る と言いたい)

↓では最初の3つ(a, b, c)が変数、残りが関数(d, e) で書いている。

<script>

// awaitを中で使うので、この start() は async で宣言する
const start = async () => {

    //////////////////////////////////////////////////////
    // (a) objectだと宣言した瞬間に実行される
    const objImmediately = new Promise((resolve) => {
        resolve();
        console.log("(a) objImmediately done");
    });

    // (b) objectだと宣言した瞬間に実行される
    const objImmediatelyWithSleep = new Promise((resolve) => {
        setTimeout(()=>{
            resolve();
            console.log("(b) objImmediatelyWithSleep done");
        }, 400)
    });

    // (c) object宣言とともに実行 & awaitでこれより下の行を一時停止する
    const objImmediatelyAndAwait = await new Promise(async (resolve) => {
        await setTimeout(async ()=>{
            resolve();
            console.log("(c) objImmediatelyAndAwait done");
        }, 300)
    });


    // (d) 関数の中で Promise objectを作る。生成即実行かと思いきや、関数が呼ばれるまでは実行されない。
    const funcLater = async () => {
        await new Promise(async (resolve) => {
            setTimeout(()=>{
                resolve();
                console.log("(d) funcLater done");
            }, 200)
        });
    }

    // (e) これも関数の中なので即実行されない。Promiseをreturnすると then が使える
    const funcLaterReturnPromise = async () => {
        return await new Promise((resolve) => {
            setTimeout(()=>{
                resolve();
                console.log("(e) funcLaterReturnPromise done");
            }, 100)
        });
    }


    // MAIN ////////////////////////////////////////////////////

    // (a, b, c) objectは関数ではないのでできない & 不要
    // objImmediately()
    // objImmediatelyWithSleep()
    // objImmediatelyAndAwait()


    // (d) 関数を呼ぶ = promiseが初めて実行される
    await funcLater();

    // 返り値がないので then はできない
    // await funcLater().then(()=>{console.log("then")});
    // Uncaught (in promise) TypeError: Cannot read property 'then' of undefined

    // (e) 返り値がpromiseだとチェーンできる
    await funcLaterReturnPromise()
    .then(
        ()=>{console.log("(e) then")
    });

    console.log("(f) Finished.");
}

start();
</script>

近頃は猫も杓子も promise をreturnしてくるので、async関数じゃないとコールすらできなかったりする。
(start()関数がわざわざ async function になってるのは中で awaitを使ってるから)

result

(a) objImmediately done
(c) objImmediatelyAndAwait done
(b) objImmediatelyWithSleep done
(d) funcLater done
(e) funcLaterReturnPromise done
(e) then
(f) Finished.

発火順 と 完了順

x = 発火
o = 実行中

objectの3つは宣言 即 実行されている。

関数の2つはその後で call されている。

発火順は上から下に a-e だ。
だけど、awaitが絡んで完了順は C が B を追い越している。その他は順番通り。
肝は、d&eのsleepがb&cより短いのに、d&eはb&cより後に完了する点だ。これは単純に発火を遅らせて実現している。

(c) がobjectだけどawaitしているので、(d)は (c) が終わるまで待っている。

(e) は (d) が終わるまで待っている。

まとめ

  1. Promise を objectとしてコールすると、即実行される。
  2. Promiseの発火タイミングをコントロールしたかったら、関数の中で宣言する。 関数をいつ呼ぶか でコントロールする。

直接 new Promise してるコードを見たら その関数がいつ呼ばれたか を知らないと即実行されたタイミングがわからない。外側の関数を見に行く必要がある。しかし大概そこでも new Promiseされている。さらに外側の関数を見に行くことになる。

僕らは無限のPromise地獄の中でもがいているのだ。