JavaScriptの基礎 - Promise

提供: MochiuWiki : SUSE, EC, PCB

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

Promiseは、非同期処理の最終的な完了または失敗を表すオブジェクトである。
ES2015 (ES6) で導入され、コールバック関数によって引き起こされるコールバック地獄を解決するための仕組みとして設計された。

Promiseを使用すると、非同期処理の成功・失敗を統一されたインターフェースで扱うことができる。
処理の結果を .then チェーンで連結することにより、非同期処理を同期処理と同様の可読性で記述できる。

Promiseの主な特徴は以下の通りである。

  • 状態管理
    pending (待機)、fulfilled (履行)、rejected (拒否) の3状態を持つ
    一度settled (確定) した状態は変更されない
  • チェーン接続
    .then.catch.finally メソッドで処理を連結できる
    各メソッドは新しいPromiseを返すため、チェーンが構築できる
  • 静的メソッド
    Promise.allPromise.race 等、複数のPromiseを組み合わせるメソッドを持つ


なお、Promiseをより簡潔に記述する async / await 構文については、JavaScriptの基礎 - async awaitのページを参照すること。


Promiseとは

Promiseの状態

Promiseは作成された時点から、以下の3つのいずれかの状態を持つ。

Promiseの3つの状態
状態 意味 値の有無
pending (待機) 初期状態
処理が完了していない状態
値なし
fulfilled (履行) 処理が成功して完了した状態 value (結果値) を持つ
rejected (拒否) 処理が失敗した状態 reason (拒否理由) を持つ


pendingからfulfilled または rejected へ遷移すると、その後状態は変化しない。
fulfilled および rejected の状態をまとめて settled (確定) と呼ぶ。

状態遷移は一方向であり、1度settledになったPromiseの状態を後から変更することはできない。

new Promise

new Promise(executor) でPromiseオブジェクトを作成する。
executorは、Promise生成時に同期的に呼び出される関数であり、resolvereject の2つの引数を受け取る。

基本構文を以下に示す。

 const promise = new Promise((resolve, reject) => {
    // 非同期処理または同期処理を記述する
    if (/* 処理が成功 */ true) {
       resolve(value);   // fulfilled状態に変更する
    }
    else {
       reject(reason);   // rejected状態に変更する
    }
 });


executor内での各関数の動作を以下に示す。

Promiseコンストラクタのコールバック引数
引数 説明
resolve(value)
  • Promiseをfulfilled状態に変更し、valueを結果として保持する。
  • valueが別のPromiseの場合、そのPromiseが解決されるまで待機する。
  • valueが通常の値の場合、そのまま結果値となる。
  • resolvereject は最初の呼び出しのみが有効であり、
    複数回呼び出しても2回目以降は無視される。
reject(reason)
  • Promiseをrejected状態に変更し、reasonを拒否理由として保持する。
  • reasonは、一般的にErrorオブジェクトを渡すが、任意の値を指定できる。


executor内で例外が発生した場合、resolve / reject がまだ呼ばれていなければ、Promiseは自動的にrejected状態となる。

実用的な使用例を以下に示す。

 // n秒後に値を返すPromiseを作成する
 function delay(ms) {
    return new Promise((resolve) => {
       setTimeout(() => resolve(ms), ms);
    });
 }
 
 // 成功と失敗の両方を持つPromiseの例
 function fetchData(shouldFail) {
    return new Promise((resolve, reject) => {
       if (shouldFail) {
          reject(new Error("データの取得に失敗した"));
       }
       else {
          resolve({ id: 1, name: "Alice" });
       }
    });
 }
 
 // 使用例
 delay(1000).then(ms => console.log(ms + "ミリ秒が経過した"));
 // 1秒後: "1000ミリ秒が経過した"



thenチェーン

.then

.then(onFulfilled, onRejected) は、Promiseがsettledになった後に実行するコールバックを登録するメソッドである。
常に新しいPromiseを返すため、複数の .then を連結してチェーンを構築できる。

thenメソッドのコールバック引数
引数 説明
onFulfilled
  • Promiseがfulfilled状態になった時に実行される。
  • 引数にPromiseの結果値 (value) を受け取る。
onRejected
  • Promiseがrejected状態になった時に実行される。
  • 引数に拒否理由 (reason) を受け取る。
  • 省略した場合は、rejectedをそのまま次のPromiseに伝播する。


.then の戻り値のルールを以下に示す。

thenメソッドの戻り値の挙動
条件 挙動
コールバックが値を返した場合 その値でfulfilledになる新しいPromiseを返す。
コールバックがPromiseを返した場合 その返されたPromiseが解決されるまで待機する。
コールバックで例外が発生した場合 その例外でrejectedになる新しいPromiseを返す。


使用例を以下に示す。

 // .thenチェーンで値を変換しながら伝播する
 Promise.resolve(1)
    .then(value => {
       console.log(value);  // 1
       return value + 1;    // 次のthenにvalue=2が渡される
    })
    .then(value => {
       console.log(value);  // 2
       return value * 10;   // 次のthenにvalue=20が渡される
    })
    .then(value => {
       console.log(value);  // 20
    });
 
 // Promiseを返すことで非同期処理を直列につなぐ
 function fetchUser(id) {
    return Promise.resolve({ id, name: "Alice" });
 }
 
 function fetchPosts(userId) {
    return Promise.resolve([{ id: 1, title: "最初の投稿", userId }]);
 }
 
 fetchUser(1)
    .then(user => {
       console.log("ユーザ:", user.name);
       return fetchPosts(user.id);  // Promiseを返す
    })
    .then(posts => {
       console.log("投稿数:", posts.length);
    });


.catch

.catch(onRejected) は、Promiseがrejected状態になった時のコールバックを登録するメソッドである。
.then(null, onRejected) と同等であり、チェーン内の任意の位置で発生したエラーをキャッチできる。

onRejected 内で値を返した場合、その値でfulfilledになる新しいPromiseを返す。
これにより、エラーから回復してチェーンを継続することができる。

 // エラー回復の例
 Promise.reject(new Error("最初のエラー"))
    .catch(error => {
       console.log("エラーをキャッチ:", error.message);
       return "回復した値";  // エラーから回復してチェーンを継続する
    })
    .then(value => {
       console.log("回復後の値:", value);  // "回復した値"
    });
 
 // チェーン内のエラーをまとめてキャッチする
 fetchUser(1)
    .then(user => fetchPosts(user.id))
    .then(posts => fetchComments(posts[0].id))
    .catch(error => {
       // どのステップで発生したエラーもここでキャッチできる
       console.error("処理に失敗した:", error.message);
    });


.finally

.finally(onFinally) は、Promiseがfulfilled または rejected のいずれの状態になった場合も実行されるコールバックを登録するメソッドである。

.finally の特徴を以下に示す。

  • onFinally は引数を受け取らない。(成功・失敗の結果は渡されない)
  • 元のPromiseの状態と結果値がそのまま保持される。(通過する)
  • リソースの解放やローディング状態の解除等、クリーンアップ処理に使用する。


 // ローディング表示のクリーンアップ例
 function fetchWithLoading(url) {
    showLoadingSpinner();  // ローディング表示開始
 
    return fetch(url)
       .then(response => response.json())
       .catch(error => {
          console.error("取得に失敗した:", error.message);
          throw error;  // エラーを再スローして呼び出し元に伝える
       })
       .finally(() => {
          hideLoadingSpinner();  // 成功・失敗に関わらずローディングを非表示にする
       });
 }
 
 // .finallyの戻り値は元のPromiseの結果を引き継ぐ
 Promise.resolve("成功値")
    .finally(() => {
       console.log("finallyが実行された");
       // return "別の値";  // この戻り値は無視される
    })
    .then(value => {
       console.log(value);  // "成功値" (元の値が保持される)
    });


チェーンの動作原理

.then.catch.finally は常に新しいPromiseを返す。
この性質により、各メソッドをチェーン状に連結することができる。

コールバックの戻り値が次のPromiseの状態を決定する仕組みを以下に示す。

 // チェーンの動作原理を示す例
 const p1 = Promise.resolve("初期値");
 
 const p2 = p1.then(value => {
    // 通常の値を返す → p2はその値でfulfilledになる
    return value + " -> 変換後";
 });
 
 const p3 = p2.then(value => {
    // Promiseを返す → p3はそのPromiseの解決を待つ
    return new Promise(resolve => {
       setTimeout(() => resolve(value + " -> 非同期処理後"), 100);
    });
 });
 
 const p4 = p3.then(value => {
    // 例外を投げる → p4はrejectedになる
    throw new Error("意図的なエラー");
 });
 
 const p5 = p4.catch(error => {
    // .catchのコールバックが値を返す → p5はfulfilledになる (エラー回復)
    return "エラーから回復";
 });
 
 p5.then(value => console.log(value));  // "エラーから回復"


エラーはチェーン内を伝播し、最初の .catch に到達するまで各 .then をスキップする。

 // エラー伝播の例
 Promise.resolve("開始")
    .then(value => { throw new Error("ステップ1でエラー"); })
    .then(value => {
       // このコールバックはスキップされる (エラーが伝播している)
       console.log("スキップ:", value);
       return value;
    })
    .catch(error => {
       // エラーがここでキャッチされる
       console.log("キャッチ:", error.message);  // "ステップ1でエラー"
       return "回復した";
    })
    .then(value => {
       // .catchの後は通常のチェーンに戻る
       console.log("回復後:", value);  // "回復した"
    });



Promiseの静的メソッド

Promise.resolve / Promise.reject

Promise.resolve(value) は、指定した値でfulfilledになるPromiseを生成するショートカットである。
Promise.reject(reason) は、指定した理由でrejectedになるPromiseを生成するショートカットである。

 // Promise.resolve : 値をPromiseに変換する
 Promise.resolve(42).then(v => console.log(v));       // 42
 Promise.resolve("hello").then(v => console.log(v));  // "hello"
 
 // valueがPromiseの場合はそのまま返す (新しいPromiseでラップしない)
 const p = Promise.resolve(42);
 console.log(Promise.resolve(p) === p);               // true
 
 // Promise.reject : 拒否されたPromiseを生成する
 Promise.reject(new Error("失敗")).catch(e => console.log(e.message));  // "失敗"
 
 // 関数をPromiseを返す形式に正規化する際に使用する
 function ensurePromise(value) {
    return Promise.resolve(value);
 }
 
 ensurePromise(42).then(v => console.log(v));                   // 42
 ensurePromise(Promise.resolve(42)).then(v => console.log(v));  // 42


Promise.all

Promise.all(iterable) は、渡したPromiseの配列が全てfulfilledになるまで待機し、全ての結果を配列で返す。
1つでもrejectedになると、即座にrejectedになる。

 // 全てのPromiseが成功する場合
 const p1 = Promise.resolve("ユーザ情報");
 const p2 = Promise.resolve("注文情報");
 const p3 = Promise.resolve("在庫情報");
 
 Promise.all([p1, p2, p3])
    .then(([user, order, stock]) => {
       // 結果は元の配列の順序を維持する
       console.log(user);   // "ユーザ情報"
       console.log(order);  // "注文情報"
       console.log(stock);  // "在庫情報"
    });
 
 // 1つでも失敗すると即座にrejectされる
 Promise.all([
    Promise.resolve("成功1"),
    Promise.reject(new Error("失敗")),
    Promise.resolve("成功2"),
 ])
 .catch(error => {
    console.log(error.message);  // "失敗"
    // "成功1"や"成功2"の結果は取得できない
 });


Promise.allSettled

Promise.allSettled(iterable) は、渡したPromiseの配列が全てsettledになるまで待機する。
成功・失敗に関わらず全ての結果を収集して、各結果を { status, value } または { status, reason } 形式のオブジェクトの配列で返す。

 Promise.allSettled([
    Promise.resolve("成功"),
    Promise.reject(new Error("失敗")),
    Promise.resolve("また成功"),
 ])
 .then(results => {
    results.forEach(result => {
       if (result.status === "fulfilled") {
          console.log("成功:", result.value);
       }
       else {
          console.log("失敗:", result.reason.message);
       }
    });
 });
 // 出力:
 // "成功: 成功"
 // "失敗: 失敗"
 // "成功: また成功"


Promise.all と異なり、途中で失敗しても全ての結果を取得できる点がある。

Promise.race

Promise.race(iterable) は、渡したPromiseのうち最初に settled になったものの結果 (fulfilled または rejectedどちらでも) を返す。

 // タイムアウト処理の実装例
 function fetchWithTimeout(url, timeoutMs) {
    const fetchPromise = fetch(url);
    const timeoutPromise = new Promise((_, reject) => {
       setTimeout(() => reject(new Error("タイムアウト")), timeoutMs);
    });
 
    return Promise.race([fetchPromise, timeoutPromise]);
 }
 
 fetchWithTimeout("https://api.example.com/data", 3000)
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => {
       if (error.message === "タイムアウト") {
          console.log("3秒以内に応答がなかった");
       }
       else {
          console.log("取得に失敗した:", error.message);
       }
    });


Promise.any

Promise.any(iterable) は、渡したPromiseのうち最初にfulfilledになったものの値を返す。
全てのPromiseがrejectedになった場合のみ、AggregateError でrejectedになる。

 // フェイルオーバー (CDN冗長化) の実装例
 function fetchFromFastest(urls) {
    const requests = urls.map(url => fetch(url));
    return Promise.any(requests);
 }
 
 fetchFromFastest([
    "https://cdn1.example.com/data",
    "https://cdn2.example.com/data",
    "https://cdn3.example.com/data",
 ])
    .then(response => console.log("最速のCDNから取得成功"))
    .catch(error => {
       // AggregateErrorには全ての拒否理由が含まれる
       console.log("全てのCDNから取得に失敗した");
       console.log(error.errors);  // 各Promiseの拒否理由の配列
    });


Promise.try (ES2025)

Promise.try(fn) は、関数 fn を実行し、その結果を常にPromiseとして返す。

fn が同期的に例外を投げた場合も、rejectedなPromiseとして扱われる点がある。

ブラウザサポートは Chrome 128+、Firefox 127+、Safari 17.6+、Node.js 22.6+ 以降である。

 // Promise.tryを使用しない場合: 同期例外がPromiseの拒否として扱われない
 // Promise.resolve().then(() => riskySync()) のような回避策が必要だった
 
 // Promise.tryを使用した場合: 同期例外もPromiseの拒否として扱われる
 function riskySync() {
    throw new Error("同期エラー");
 }
 
 Promise.try(() => riskySync())
    .catch(error => {
       console.log("捕捉:", error.message);  // "捕捉: 同期エラー"
    });
 
 // 同期・非同期を問わず統一されたエラーハンドリングが実現できる
 function processInput(input) {
    return Promise.try(() => {
       if (typeof input !== "string") {
          throw new TypeError("文字列を指定すること");  // 同期例外
       }
       return fetch("/api/process?q=" + input);  // 非同期処理
    });
 }
 
 processInput(123)
    .catch(e => console.log(e.message));  // "文字列を指定すること"


下表に、各静的メソッドの動作の比較を示す。

Promiseの静的メソッド比較
メソッド 完了タイミング 成功時の戻り値 失敗時の動作 主な用途
Promise.all 全て完了まで待機 結果の配列 (順序維持) 最初の拒否で即座にreject 全て成功が必要な場合
Promise.allSettled 全て完了まで待機 status / value / reason オブジェクトの配列 全て収集 (rejectしない) 全ての結果を取得したい場合
Promise.race 最初に完了したもの 最初の結果値 最初の拒否理由 タイムアウト処理
Promise.any 最初に成功したもの 最初の成功値 全て失敗時のみ AggregateError フェイルオーバー処理
Promise.try 関数実行後 関数の戻り値または結果 同期/非同期例外を拒否として扱う 統一されたエラーハンドリング



コールバック地獄からPromiseへの移行

コールバックパターンのPromise化

Node.jsスタイルのエラーファーストコールバック (第1引数がエラー、第2引数がデータ) を持つ関数は、Promiseを返す関数に変換 (Promisification) できる。

  • コールバック地獄の例
     // Before: コールバック地獄
     // ネストが深くなりコードの可読性が著しく低下する
     function getUserData(userId, callback) {
        fetchUser(userId, function(err, user) {
           if (err) {
              callback(err);
              return;
           }
           fetchPosts(user.id, function(err, posts) {
              if (err) {
                 callback(err);
                 return;
              }
              fetchComments(posts[0].id, function(err, comments) {
                 if (err) {
                    callback(err);
                    return;
                 }
                 callback(null, { user, posts, comments });
              });
           });
        });
     }
    

  • コールバックを持つ関数をPromise化する方法
     // コールバック関数をPromiseでラップする (Promisification)
     function fetchUser(userId) {
        return new Promise((resolve, reject) => {
           fetchUserCallback(userId, (err, user) => {
              if (err) reject(err);
              else resolve(user);
           });
        });
     }
     
     function fetchPosts(userId) {
        return new Promise((resolve, reject) => {
           fetchPostsCallback(userId, (err, posts) => {
              if (err) reject(err);
              else resolve(posts);
           });
        });
     }
     
     function fetchComments(postId) {
        return new Promise((resolve, reject) => {
           fetchCommentsCallback(postId, (err, comments) => {
              if (err) reject(err);
              else resolve(comments);
           });
        });
     }
    


Promise化した関数を使用してチェーンで記述すると、ソースコードが大幅に改善される。

 // After: Promiseチェーン
 // 処理の流れが直線的になり可読性が向上する
 fetchUser(123)
    .then(user => fetchPosts(user.id))
    .then(posts => fetchComments(posts[0].id))
    .then(comments => console.log("コメント一覧:", comments))
    .catch(error => console.error("処理に失敗した:", error.message));


Node.js環境では util.promisify 関数を使用することにより、エラーファーストコールバックスタイルの関数を自動的にPromise化できる。

 // Node.jsのutil.promisifyを使用した自動Promise化
 const { promisify } = require("util");
 const fs = require("fs");
 
 // fs.readFileはコールバックスタイルの関数
 const readFileAsync = promisify(fs.readFile);
 
 readFileAsync("./data.txt", "utf8")
    .then(content => console.log(content))
    .catch(error => console.error("読み込みに失敗した:", error.message));



Tauriでの使用例

Tauri v2の invoke() 関数は、JavaScriptからRustのコマンドを呼び出す時、常にPromiseを返す。
そのため、Tauriのフロントエンド開発においてPromiseは中心的な役割を担う。

  • 基本
     import { invoke } from '@tauri-apps/api/core';
     
     // Rustコマンドを呼び出してPromiseチェーンで処理する
     invoke('greet', { name: 'World' })
        .then(message => {
           document.getElementById('result').textContent = message;
        })
        .catch(error => {
           console.error('コマンドの実行に失敗した:', error);
        });
    

    対応するRust側のコマンド定義
     #[tauri::command]
     async fn greet(name: &str) -> Result<String, String> {
        Ok(format!("Hello, {}!", name))
     }
    

  • 複数のコマンドをチェーンで連結する例
     import { invoke } from '@tauri-apps/api/core';
     
     // ファイルを読み込んで内容を処理するパイプライン
     invoke('read_file', { path: '/home/user/data.json' })
        .then(content => {
           const data = JSON.parse(content);
           return invoke('process_data', { data });
        })
        .then(result => {
           return invoke('save_result', { result, path: '/home/user/output.json' });
        })
        .then(() => {
           console.log('処理が完了した');
        })
        .catch(error => {
           console.error('エラーが発生した:', error);
        })
        .finally(() => {
           // ローディングスピナーを非表示にする等のクリーンアップ処理
           setLoading(false);
        });
    

  • 複数のRustコマンドを並列実行する例
     import { invoke } from '@tauri-apps/api/core';
     
     // Promise.allで複数のRustコマンドを並列実行する
     Promise.all([
        invoke('get_system_info'),
        invoke('get_app_config'),
        invoke('get_user_preferences'),
     ])
        .then(([systemInfo, config, preferences]) => {
           console.log('システム情報:', systemInfo);
           console.log('アプリ設定:', config);
           console.log('ユーザ設定:', preferences);
        })
        .catch(error => {
           console.error('情報の取得に失敗した:', error);
        });
    



関連情報