my.code(); Logomy.code();
JavaScript-8.非同期処理①: Promise

my.code(); Logomy.code();

  • C++
    • 0.C++の世界へようこそ
    • 1.型システムと制御構造
    • 2.データ集合とモダンな操作
    • 3.ポインタとメモリ管理
    • 4.関数と参照渡し
    • 5.プロジェクトの分割とビルド
    • 6.クラスの基礎
    • 7.クラスを使いこなす
    • 8.継承とポリモーフィズム
    • 9.テンプレート
    • 10.STL ①:コンテナ
    • 11.STL ②:アルゴリズムとラムダ式
    • 12.RAIIとスマートポインタ
  • JavaScript
    • 0.JavaScriptへようこそ
    • 1.基本構文とデータ型
    • 2.制御構文
    • 3.関数とクロージャ
    • 4.'this'の正体
    • 5.オブジェクトとプロトタイプ
    • 6.クラス構文
    • 7.配列とイテレーション
    • 8.非同期処理①: Promise
    • 9.非同期処理②: Async/Await
  • Python
    • 0.環境構築と基本思想
    • 1.基本構文とデータ型
    • 2.リスト、タプル、辞書、セット
    • 3.制御構文と関数
    • 4.モジュールとパッケージ
    • 5.オブジェクト指向プログラミング
    • 6.ファイルの入出力とコンテキストマネージャ
    • 7.例外処理
    • 8.ジェネレータとデコレータ
  • Ruby
    • 0.rubyの世界へようこそ
    • 1.基本構文とデータ型
    • 2.制御構造とメソッド定義
    • 3.すべてがオブジェクト
    • 4.コレクション (Array, Hash, Range)
    • 5.ブロックとイテレータ
    • 6.クラスとオブジェクト
    • 7.モジュールとMix-in
    • 8.Proc, Lambda, クロージャ
    • 9.標準ライブラリの活用
    • 10.テスト文化入門
    • 11.メタプログラミング入門
  • Rust
    • 0.Rustの世界へようこそ
    • 1.基本構文と「不変性」
    • 2.関数と制御フロー
    • 3.所有権
    • 4.借用とスライス
    • 5.構造体とメソッド構文
    • 6.列挙型とパターンマッチ
    • 7.モジュールシステムとパッケージ管理
    • 8.コレクションと文字列
    • 9.エラーハンドリング
    • 10.ジェネリクスとトレイト
    • 11.ライフタイム
  • TypeScript
    • 0.TypeScriptへようこそ
    • 1.基本的な型と型推論
    • 2.オブジェクト、インターフェース、型エイリアス
    • 3.関数の型定義
    • 4.型を組み合わせる
    • 5.ジェネリクス
    • 6.クラスとアクセス修飾子
    • 7.非同期処理とユーティリティ型
my.code(); Logomy.code();

環境構築不要、その場で実践。

ut-code / my-code

Copyright © 2026 ut.code();

ut.code(); について
公式ウェブサイト公式 𝕏 アカウント

第8章: 非同期処理(1)- Promise

JavaScriptは基本的にシングルスレッドで動作します。つまり、一度に一つの処理しか実行できません。しかし、ネットワークリクエストやタイマーなどの重い処理を行っている間、ブラウザがフリーズしたりサーバーが応答しなくなったりしては困ります。

そこでJavaScriptは、重い処理をバックグラウンド(Web APIsやNode.jsのC++レイヤー)に任せ、完了通知を受け取ることで並行処理のような動きを実現しています。

本章では、JavaScriptの非同期処理の基盤となるメカニズムと、それを現代的に扱うための標準APIである Promise について解説します。

同期処理 vs 非同期処理

まず、挙動の違いを確認しましょう。

  • 同期処理 (Synchronous): コードが上から下へ順番に実行されます。前の処理が終わるまで次の処理は待たされます(ブロッキング)。
  • 非同期処理 (Asynchronous): 処理の完了を待たずに、即座に次のコードへ進みます(ノンブロッキング)。処理結果は後でコールバックなどを通じて受け取ります。

以下のコードは、setTimeout(非同期API)を使用した例です。他言語の経験者であれば、「Start」→「1秒待機」→「Timer」→「End」と予想するかもしれませんが、JavaScriptでは異なります。

ファイルを編集:async_demo.js
console.log('1. Start');

// 1000ミリ秒後にコールバックを実行する非同期関数
setTimeout(() => {
    console.log('2. Timer fired');
}, 1000);

console.log('3. End');
node async_demo.js
ブラウザ上で動作するJavaScriptの実行環境です。
左上の実行ボタンを押して、このページ内のasync_demo.jsに書かれている内容を実行します。
1. Start
3. End
2. Timer fired

setTimeout は「タイマーをセットする」という命令だけを出し、即座に制御を返します。そのため、タイマーの発火を待たずに 3. End が出力されます。

イベントループとコールバックキューの仕組み

なぜシングルスレッドで非同期処理が可能なのか、その裏側にあるのが イベントループ (Event Loop) という仕組みです。

JavaScriptのランタイムは主に以下の要素で構成されています:

  1. コールスタック (Call Stack): 現在実行中の関数が積まれる場所。LIFO(後入れ先出し)。
  2. Web APIs / Node APIs: ブラウザやOSが提供する機能(タイマー、Fetch、DOMイベントなど)。非同期処理はここで実行されます。
  3. コールバックキュー (Callback Queue): 非同期処理が完了した後、実行待ちのコールバック関数が並ぶ列。
  4. イベントループ (Event Loop): コールスタックとキューを監視する仕組み。

処理の流れ:

  1. setTimeout がコールスタックで実行されると、ブラウザのタイマーAPIに処理を依頼し、スタックから消えます。
  2. 指定時間が経過すると、タイマーAPIはコールバック関数を コールバックキュー に入れます。
  3. イベントループ は、「コールスタックが空(メインの処理が完了)」かつ「キューにタスクがある」場合、キューからタスクを取り出してコールスタックへ移動させます。

この仕組みにより、メインスレッドをブロックすることなく非同期処理を実現しています。

コールバック地獄の問題点

Promiseが登場する以前(ES5時代まで)は、非同期処理の順序制御を行うために、コールバック関数を入れ子にする手法が一般的でした。

例えば、「処理Aが終わったら処理B、その後に処理C...」というコードを書こうとすると、以下のようにネストが深くなります。

ファイルを編集:callback_hell.js
function delay(ms, callback) {
    setTimeout(callback, ms);
}

console.log('Start');

delay(1000, () => {
    console.log('Step 1 finished');
    
    delay(1000, () => {
        console.log('Step 2 finished');
        
        delay(1000, () => {
            console.log('Step 3 finished');
            console.log('End');
        });
    });
});
node callback_hell.js
ブラウザ上で動作するJavaScriptの実行環境です。
左上の実行ボタンを押して、このページ内のcallback_hell.jsに書かれている内容を実行します。
Start
Step 1 finished
Step 2 finished
Step 3 finished
End

これはいわゆる 「コールバック地獄 (Callback Hell)」 と呼ばれる状態で、可読性が低く、エラーハンドリングも困難です。これを解決するために導入されたのが Promise です。

Promiseの概念

Promise は、非同期処理の「最終的な完了(または失敗)」とその「結果の値」を表すオブジェクトです。未来のある時点で値が返ってくる「約束手形」のようなものと考えてください。

Promiseオブジェクトは以下の3つの状態のいずれかを持ちます。

  1. Pending (待機中): 初期状態。処理はまだ完了していない。
  2. Fulfilled (履行): 処理が成功し、値を持っている状態。(resolve された)
  3. Rejected (拒否): 処理が失敗し、エラー理由を持っている状態。(reject された)

Promiseの状態は一度 Pending から Fulfilled または Rejected に変化すると、二度と変化しません(Immutable)。

Promiseの使い方

Promiseの作成

new Promise コンストラクタを使用します。引数には (resolve, reject) を受け取る関数(Executor)を渡します。

JavaScript 実行環境
ブラウザ上で動作するJavaScriptのREPL実行環境です。
プロンプト (>) の後にコマンドを入力し、Enterキーで実行します。
Ctrl+Cまたは左上の停止ボタンで実行中のコマンドを中断できます。
> const myPromise = new Promise((resolve, reject) => {
...   // ここで非同期処理を行う
...   const success = true;
...   if (success) {
...     resolve("OK!"); // 成功時
...   } else {
...     reject(new Error("Failed")); // 失敗時
...   }
... });
undefined
> myPromise
Promise { 'OK!' }

.then(), .catch(), .finally()

Promiseオブジェクトの結果を受け取るには、以下のメソッドを使用します。

  • .then(onFulfilled): PromiseがFulfilledになった時に実行されます。
  • .catch(onRejected): PromiseがRejectedになった時に実行されます。
  • .finally(onFinally): 成功・失敗に関わらず、処理終了時に実行されます。

先ほどのコールバック地獄の例を、Promiseを使って書き直してみましょう。

ファイルを編集:promise_chain.js
// Promiseを返す関数を作成
function delay(ms) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(`Waited ${ms}ms`);
        }, ms);
    });
}

console.log('Start');

delay(1000)
    .then((message) => {
        console.log('Step 1:', message);
        // 次のPromiseを返すことでチェーンをつなぐ
        return delay(1000);
    })
    .then((message) => {
        console.log('Step 2:', message);
        return delay(1000);
    })
    .then((message) => {
        console.log('Step 3:', message);
        console.log('End');
    })
    .catch((error) => {
        // チェーンのどこかでエラーが起きればここに飛ぶ
        console.error('Error:', error);
    });
node promise_chain.js
ブラウザ上で動作するJavaScriptの実行環境です。
左上の実行ボタンを押して、このページ内のpromise_chain.jsに書かれている内容を実行します。
Start
Step 1: Waited 1000ms
Step 2: Waited 1000ms
Step 3: Waited 1000ms
End

重要なポイント:

  1. .then() の中で新しい Promise を返すと、次の .then() はその新しい Promise の完了を待ちます。これにより、非同期処理を フラットな連鎖 として記述できます。
  2. エラー処理は最後の .catch() に集約できます。try-catch ブロックに近い感覚で扱えるようになります。

この章のまとめ

  • JavaScriptはシングルスレッドで動作し、イベントループ という仕組みを使って非同期処理を管理しています。
  • 非同期処理の完了を待つために、昔はコールバック関数が多用されていましたが、ネストが深くなる問題がありました。
  • Promise は非同期処理の状態(Pending, Fulfilled, Rejected)を管理するオブジェクトです。
  • .then() をチェーンさせることで、非同期処理を直列に、読みやすく記述できます。
  • エラーハンドリングは .catch() で一括して行えます。

次章では、このPromiseをさらに同期処理のように書ける構文糖衣 async/await について学びます。

練習問題1: ランダムな成功/失敗

Math.random() を使い、50%の確率で成功(Resolve)、50%の確率で失敗(Reject)するPromiseを返す関数 coinToss を作成してください。 それを使用し、成功時は "Win!"、失敗時は "Lose..." とコンソールに表示するコードを書いてください。

ファイルを編集:practice9_1.js
node practice9_1.js
ブラウザ上で動作するJavaScriptの実行環境です。
左上の実行ボタンを押して、このページ内のpractice9_1.jsに書かれている内容を実行します。

練習問題2: 擬似的なデータ取得フロー

以下の仕様を満たすコードを作成してください。

  1. 関数 fetchUser(userId): 1秒後に { id: userId, name: "User" + userId } というオブジェクトでresolveする。
  2. 関数 fetchPosts(userName): 1秒後に ["Post 1 by " + userName, "Post 2 by " + userName] という配列でresolveする。
  3. これらをPromiseチェーンで繋ぎ、ユーザーID 1 でユーザーを取得した後、その名前を使って投稿を取得し、最終的に投稿リストをコンソールに表示してください。
ファイルを編集:practice9_2.js
node practice9_2.js
ブラウザ上で動作するJavaScriptの実行環境です。
左上の実行ボタンを押して、このページ内のpractice9_2.jsに書かれている内容を実行します。
前のページ« 配列とイテレーション
次のページ非同期処理②: Async/Await »