📑

非同期処理つまづきポイント

に公開
2

はじめに

これまでJavaScriptの非同期処理やPromiseについて学習していく中で、誤解していたことや自分でコードを書いたときに間違えていたことがありました。
今回はそのつまづいたポイントを2つについて考えてみようと思います。

つまづいているコード

過去私が間違えた以下のコードでは、つまづいたポイント3つすべてが含まれています🤔
どういう出力になるのかぜひ考えてみてください。

const test = new Promise((resolve) => 
    {
        console.log(2);
        setTimeout(() => {
            console.log(3);
            resolve();
        }, 1000);
    })

const main = () => {
    console.log(1);
    test.then(() => {
        console.log(4);
    });
};

main();
出力

2
1
3
4

① 非同期コールバックは同期的に呼び出してはならない

注目してほしいのは、この箇所👇

const test = new Promise((resolve) => 
    {
        console.log(2);
        setTimeout(() => {
            console.log(3);
            resolve();
        }, 1000);
    })

このコードでは、Promise オブジェクトを変数に直接代入しています。こうすると、main 関数が呼び出される前に test の中身が実行されてしまい、まず 2 が出力されます。

なぜかというと、JavaScriptでは new Promise(...) の中に書かれた関数(executor関数)は、Promise を作成した瞬間に同期的に実行されるという仕様になっているからです。

また、3 がすぐに出力されないのは、setTimeout のコールバックが非同期タスクとして遅れて実行されるからです。その間に、main() の中にある 1 が先に実行されます。

非同期処理についてきちんと理解するまで、「Promise の中に書いた処理はすべて非同期になる」と思いがちです。しかし実際には、非同期になるのは setTimeout や fetch のような非同期APIの部分、または .then/.catch の中の処理だけであり、Promise コンストラクタの中に書かれた処理(executor)は即時に同期実行されます

修正します

//テストを関数に変更
const test = () => {
    new Promise((resolve) => {
        console.log(2);
        setTimeout(() => {
            console.log(3);
            resolve();
        }, 1000);
    })
}

const main = () => {
    console.log(1);
//testを関数にしたので呼び方を変更
    test().then(() => {
        console.log(4);
    });
};

main();
出力

1
2

3

あれ、エラーが出たぞ。なぜでしょうか?

② returnでPromiseを返す

Promise内の関数の実行結果を返していないため、then内の処理に進めない状態になっています。
test() 関数が Promise を返していないため、.then(...) を呼ぼうとしたときに undefined.then となってエラーになります。
これ簡単そうで意外と陥るミスみたいです。

修正します

const test = () => {
    <!-- returnで実行結果を返します -->
    return new Promise((resolve) => 
    {
        console.log(2);
        setTimeout(() => {
            console.log(3);
            resolve();
        }, 1000);
    });
}

const main = () => {
    console.log(1);
    test().then(() => {
        console.log(4);
    });
};

main();
出力

1
2
3
4

まとめ

  • Promise内の関数は作成された瞬間に即実行される。非同期になるのは、then/catchの中の処理
  • Promiseはreturnで返さないと、then/catch内の処理が実行されない
ポイント 内容
Promiseのexecutorは同期 new Promise(...) 内の処理は即時実行される
.then()Promise にのみ使える 関数が Promise を返さないと .then(...) は呼べない
setTimeout, fetch などは非同期 setTimeout(...) の中は非同期コールバックで後から実行される
return を忘れるとエラーになる Promise を返さないと undefined.then(...) でエラーになる

追記

非同期コールバックには2つの種類が合って、microtaskにタスクを一時保持するものとmacrotaskに保持するものがあります。これはイベントループについての理解が必要です。
それを理解するとより一層今回の各コードの出力が理解できると思います。

以下の記事がイベントループについて先日まとめたものです。
ざっくりEvent Loopを理解する

Discussion

YuneKichiYuneKichi

「修正します」のコード、実際に試したときには new Promiseの式が { } で囲われていませんでしたか。
現在書かれているコードだと、ラムダ式の本体が式であるため、Promiseオブジェクトがかえり正しく処理されます。

akinkoakinko

コメントありがとうございます!その通りでした!そこら辺の理解が曖昧でした。新しい気づきがありました。ご指摘ありがとうございました🙏