MochiuWiki : SUSE, EC, PCB
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
JavaScriptの基礎 - Promiseのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
JavaScriptの基礎 - Promise
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == Promiseは、非同期処理の最終的な完了または失敗を表すオブジェクトである。<br> ES2015 (ES6) で導入され、コールバック関数によって引き起こされるコールバック地獄を解決するための仕組みとして設計された。<br> <br> Promiseを使用すると、非同期処理の成功・失敗を統一されたインターフェースで扱うことができる。<br> 処理の結果を <code>.then</code> チェーンで連結することにより、非同期処理を同期処理と同様の可読性で記述できる。<br> <br> Promiseの主な特徴は以下の通りである。<br> * 状態管理 *: pending (待機)、fulfilled (履行)、rejected (拒否) の3状態を持つ *: 一度settled (確定) した状態は変更されない * チェーン接続 *: <code>.then</code>、<code>.catch</code>、<code>.finally</code> メソッドで処理を連結できる *: 各メソッドは新しいPromiseを返すため、チェーンが構築できる * 静的メソッド *: <code>Promise.all</code>、<code>Promise.race</code> 等、複数のPromiseを組み合わせるメソッドを持つ <br> なお、Promiseをより簡潔に記述する <code>async</code> / <code>await</code> 構文については、[[JavaScriptの基礎 - async await]]のページを参照すること。<br> <br><br> == Promiseとは == ==== Promiseの状態 ==== Promiseは作成された時点から、以下の3つのいずれかの状態を持つ。<br> <br> <center> {| class="wikitable" |+ Promiseの3つの状態 ! 状態 !! 意味 !! 値の有無 |- | pending (待機) || 初期状態<br>処理が完了していない状態 || 値なし |- | fulfilled (履行) || 処理が成功して完了した状態 || value (結果値) を持つ |- | rejected (拒否) || 処理が失敗した状態 || reason (拒否理由) を持つ |} </center> <br> pendingからfulfilled または rejected へ遷移すると、その後状態は変化しない。<br> fulfilled および rejected の状態をまとめて <u>settled (確定)</u> と呼ぶ。<br> <br> 状態遷移は一方向であり、1度settledになったPromiseの状態を後から変更することはできない。<br> <br> ==== new Promise ==== <code>new Promise(executor)</code> でPromiseオブジェクトを作成する。<br> executorは、Promise生成時に同期的に呼び出される関数であり、<code>resolve</code> と <code>reject</code> の2つの引数を受け取る。<br> <br> 基本構文を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> const promise = new Promise((resolve, reject) => { // 非同期処理または同期処理を記述する if (/* 処理が成功 */ true) { resolve(value); // fulfilled状態に変更する } else { reject(reason); // rejected状態に変更する } }); </syntaxhighlight> <br> executor内での各関数の動作を以下に示す。<br> <br> <center> {| class="wikitable" |+ Promiseコンストラクタのコールバック引数 ! 引数 !! 説明 |- | <code>resolve(value)</code> | * Promiseをfulfilled状態に変更し、valueを結果として保持する。 * valueが別のPromiseの場合、そのPromiseが解決されるまで待機する。 * valueが通常の値の場合、そのまま結果値となる。 * <code>resolve</code> と <code>reject</code> は最初の呼び出しのみが有効であり、<br>複数回呼び出しても2回目以降は無視される。 |- | <code>reject(reason)</code> | * Promiseをrejected状態に変更し、reasonを拒否理由として保持する。 * reasonは、一般的にErrorオブジェクトを渡すが、任意の値を指定できる。 |} </center> <br> <u>executor内で例外が発生した場合、<code>resolve</code> / <code>reject</code> がまだ呼ばれていなければ、Promiseは自動的にrejected状態となる。</u><br> <br> 実用的な使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> // 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ミリ秒が経過した" </syntaxhighlight> <br><br> == thenチェーン == ==== .then ==== <code>.then(onFulfilled, onRejected)</code> は、Promiseがsettledになった後に実行するコールバックを登録するメソッドである。<br> 常に新しいPromiseを返すため、複数の <code>.then</code> を連結してチェーンを構築できる。<br> <br> <center> {| class="wikitable" |+ thenメソッドのコールバック引数 ! 引数 !! 説明 |- | <code>onFulfilled</code> | * Promiseがfulfilled状態になった時に実行される。 * 引数にPromiseの結果値 (value) を受け取る。 |- | <code>onRejected</code> | * Promiseがrejected状態になった時に実行される。 * 引数に拒否理由 (reason) を受け取る。 * <u>省略した場合は、rejectedをそのまま次のPromiseに伝播する。</u> |} </center> <br> <code>.then</code> の戻り値のルールを以下に示す。<br> <br> <center> {| class="wikitable" |+ thenメソッドの戻り値の挙動 ! 条件 !! 挙動 |- | コールバックが値を返した場合 || その値でfulfilledになる新しいPromiseを返す。 |- | コールバックがPromiseを返した場合 || その返されたPromiseが解決されるまで待機する。 |- | コールバックで例外が発生した場合 || その例外でrejectedになる新しいPromiseを返す。 |} </center> <br> 使用例を以下に示す。<br> <br> <syntaxhighlight lang="javascript"> // .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); }); </syntaxhighlight> <br> ==== .catch ==== <code>.catch(onRejected)</code> は、Promiseがrejected状態になった時のコールバックを登録するメソッドである。<br> <code>.then(null, onRejected)</code> と同等であり、チェーン内の任意の位置で発生したエラーをキャッチできる。<br> <br> <code>onRejected</code> 内で値を返した場合、その値でfulfilledになる新しいPromiseを返す。<br> これにより、エラーから回復してチェーンを継続することができる。<br> <br> <syntaxhighlight lang="javascript"> // エラー回復の例 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); }); </syntaxhighlight> <br> ==== .finally ==== <code>.finally(onFinally)</code> は、Promiseがfulfilled または rejected のいずれの状態になった場合も実行されるコールバックを登録するメソッドである。<br> <br> <code>.finally</code> の特徴を以下に示す。<br> <br> * <code>onFinally</code> は引数を受け取らない。(成功・失敗の結果は渡されない) * 元のPromiseの状態と結果値がそのまま保持される。(通過する) * リソースの解放やローディング状態の解除等、クリーンアップ処理に使用する。 <br> <syntaxhighlight lang="javascript"> // ローディング表示のクリーンアップ例 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); // "成功値" (元の値が保持される) }); </syntaxhighlight> <br> ==== チェーンの動作原理 ==== <code>.then</code>、<code>.catch</code>、<code>.finally</code> は常に新しいPromiseを返す。<br> この性質により、各メソッドをチェーン状に連結することができる。<br> <br> コールバックの戻り値が次のPromiseの状態を決定する仕組みを以下に示す。<br> <br> <syntaxhighlight lang="javascript"> // チェーンの動作原理を示す例 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)); // "エラーから回復" </syntaxhighlight> <br> <u>エラーはチェーン内を伝播し、最初の <code>.catch</code> に到達するまで各 <code>.then</code> をスキップする。</u><br> <br> <syntaxhighlight lang="javascript"> // エラー伝播の例 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); // "回復した" }); </syntaxhighlight> <br><br> == Promiseの静的メソッド == ==== Promise.resolve / Promise.reject ==== <code>Promise.resolve(value)</code> は、指定した値でfulfilledになるPromiseを生成するショートカットである。<br> <code>Promise.reject(reason)</code> は、指定した理由でrejectedになるPromiseを生成するショートカットである。<br> <br> <syntaxhighlight lang="javascript"> // 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 </syntaxhighlight> <br> ==== Promise.all ==== <code>Promise.all(iterable)</code> は、渡したPromiseの配列が全てfulfilledになるまで待機し、全ての結果を配列で返す。<br> 1つでもrejectedになると、即座にrejectedになる。<br> <br> <syntaxhighlight lang="javascript"> // 全ての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"の結果は取得できない }); </syntaxhighlight> <br> ==== Promise.allSettled ==== <code>Promise.allSettled(iterable)</code> は、渡したPromiseの配列が全てsettledになるまで待機する。<br> 成功・失敗に関わらず全ての結果を収集して、各結果を <code>{ status, value }</code> または <code>{ status, reason }</code> 形式のオブジェクトの配列で返す。<br> <br> <syntaxhighlight lang="javascript"> 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); } }); }); // 出力: // "成功: 成功" // "失敗: 失敗" // "成功: また成功" </syntaxhighlight> <br> <u><code>Promise.all</code> と異なり、途中で失敗しても全ての結果を取得できる点がある。</u><br> <br> ==== Promise.race ==== <code>Promise.race(iterable)</code> は、渡したPromiseのうち最初に settled になったものの結果 (fulfilled または rejectedどちらでも) を返す。<br> <br> <syntaxhighlight lang="javascript"> // タイムアウト処理の実装例 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); } }); </syntaxhighlight> <br> ==== Promise.any ==== <code>Promise.any(iterable)</code> は、渡したPromiseのうち最初にfulfilledになったものの値を返す。<br> 全てのPromiseがrejectedになった場合のみ、<code>AggregateError</code> でrejectedになる。<br> <br> <syntaxhighlight lang="javascript"> // フェイルオーバー (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の拒否理由の配列 }); </syntaxhighlight> <br> ==== Promise.try (ES2025) ==== <code>Promise.try(fn)</code> は、関数 <code>fn</code> を実行し、その結果を常にPromiseとして返す。<br> <br> <u><code>fn</code> が同期的に例外を投げた場合も、rejectedなPromiseとして扱われる点がある。</u><br> <br> ブラウザサポートは Chrome 128+、Firefox 127+、Safari 17.6+、Node.js 22.6+ 以降である。<br> <br> <syntaxhighlight lang="javascript"> // 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)); // "文字列を指定すること" </syntaxhighlight> <br> 下表に、各静的メソッドの動作の比較を示す。<br> <br> <center> {| class="wikitable" |+ Promiseの静的メソッド比較 ! メソッド !! 完了タイミング !! 成功時の戻り値 !! 失敗時の動作 !! 主な用途 |- | Promise.all || 全て完了まで待機 || 結果の配列 (順序維持) || 最初の拒否で即座にreject || 全て成功が必要な場合 |- | Promise.allSettled || 全て完了まで待機 || status / value / reason オブジェクトの配列 || 全て収集 (rejectしない) || 全ての結果を取得したい場合 |- | Promise.race || 最初に完了したもの || 最初の結果値 || 最初の拒否理由 || タイムアウト処理 |- | Promise.any || 最初に成功したもの || 最初の成功値 || 全て失敗時のみ AggregateError || フェイルオーバー処理 |- | Promise.try || 関数実行後 || 関数の戻り値または結果 || 同期/非同期例外を拒否として扱う || 統一されたエラーハンドリング |} </center> <br><br> == コールバック地獄からPromiseへの移行 == ==== コールバックパターンのPromise化 ==== Node.jsスタイルのエラーファーストコールバック (第1引数がエラー、第2引数がデータ) を持つ関数は、Promiseを返す関数に変換 (Promisification) できる。<br> <br> * コールバック地獄の例 *: <syntaxhighlight lang="javascript"> // 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 }); }); }); }); } </syntaxhighlight> *: <br> * コールバックを持つ関数をPromise化する方法 *: <syntaxhighlight lang="javascript"> // コールバック関数を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); }); }); } </syntaxhighlight> <br> <u>Promise化した関数を使用してチェーンで記述すると、ソースコードが大幅に改善される。</u><br> <br> <syntaxhighlight lang="javascript"> // 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)); </syntaxhighlight> <br> Node.js環境では <code>util.promisify</code> 関数を使用することにより、エラーファーストコールバックスタイルの関数を自動的にPromise化できる。<br> <br> <syntaxhighlight lang="javascript"> // 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)); </syntaxhighlight> <br><br> == Tauriでの使用例 == <u>Tauri v2の <code>invoke()</code> 関数は、JavaScriptからRustのコマンドを呼び出す時、常にPromiseを返す。</u><br> <u>そのため、Tauriのフロントエンド開発においてPromiseは中心的な役割を担う。</u><br> <br> * 基本 *: <syntaxhighlight lang="javascript"> 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); }); </syntaxhighlight> *: <br> *: 対応するRust側のコマンド定義 *: <syntaxhighlight lang="rust"> #[tauri::command] async fn greet(name: &str) -> Result<String, String> { Ok(format!("Hello, {}!", name)) } </syntaxhighlight> *: <br> * 複数のコマンドをチェーンで連結する例 *: <syntaxhighlight lang="javascript"> 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); }); </syntaxhighlight> *: <br> * 複数のRustコマンドを並列実行する例 *: <syntaxhighlight lang="javascript"> 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); }); </syntaxhighlight> <br><br> == 関連情報 == * [[JavaScriptの基礎 - async await]] *: Promiseをより簡潔に記述するためのasync / await構文の詳細 * [[JavaScriptの基礎 - イベントループ]] *: JavaScriptの非同期処理を支えるイベントループの仕組み * [[JavaScriptの基礎 - Fetch API]] *: Promiseを返すFetch APIによるHTTPリクエストの実装 * [[JavaScriptの基礎 - コールバック関数]] *: Promiseが解決した課題の背景となるコールバックパターンの詳細 <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Web]]
JavaScriptの基礎 - Promise
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse