JavaScript Promise / Async Function の使い方
JavaScript の Promise や Async Function(async/await)を使った非同期処理の基本的な使い方についての解説のような覚書です。
作成日:2020年6月17日
関連ページ:
参考サイト
- JavaScript Primer/非同期処理
- JavaScript Promiseの本
- developers.google.com/JavaScript の Promise: 概要
- ja.javascript.info/導入: callbacks
- ja.javascript.info/Promises チェーン
- developer.mozilla.org/Promise
- techblog.yahoo.co.jp/Callback を撲滅せよ
- ja.javascript.info/ Async/await
- developer.mozilla.org/async function
- developer.mozilla.org/await
同期処理と非同期処理
プログラムの処理は同期処理(sync)と非同期処理(async)に分類することができます。
同期処理は記述されたコードを順番に処理し、1つの処理が終了すると次の処理を実行します。そのため、どれかの処理に時間がかかるとその処理が終了するまでその次の処理に進むことができません。
以下の oneSecMsg() は1秒経過したらメッセージを表示して終了する関数で、この関数を実行すると1秒間処理がブロックされます。
以下を実行すると、「Start」とすぐに表示され、oneSecMsg() により同期的にブロックされ1秒後に「一秒経過」と表示され、その後すぐに「End」と表示されます。
function oneSecMsg(msg) { //1秒経過したらメッセージを表示する関数 // 開始時刻 const start = Date.now(); // 1秒経過するまでループ while (true) { //現在の時刻と開始時刻の差 const diff = Date.now() - start; if (diff >= 1000) { // 1秒経過したらメッセージを表示して終了 console.log(msg); return; } } } console.log('Start'); oneSecMsg('一秒経過'); console.log('End'); //出力結果 Start 一秒経過 End
非同期処理
非同期処理は記述されたコードを順番に処理しますが、1つの処理が終了するのを待たずに次の処理を実行する(次の処理へ進む)ことができます。
JavaScript の setTimeout() は指定した遅延時間が経過するとコールバック関数を呼び出す非同期処理の関数です。setTimeout() が実行された後、JavaScript はコールバック関数の結果を待たずに次の処理に進むことができます。
以下は setTimeout() に指定したコールバック関数を使って1秒後にメッセージを表示しています。
setTimeout() に指定したコールバック関数は非同期的なタイミングで呼ばれる(ノンブロッキングな処理である)ため、次の行(9行目)の同期的処理は、非同期処理の setTimeout() のコールバック関数よりも先に実行されます(コードの記述順とは異なる順番で実行されます)。
console.log('Start'); //1000ミリ秒後にコールバック関数を実行 setTimeout(() => { //コールバック関数 console.log('一秒後に表示'); }, 1000); console.log('End'); //上記コールバック関数より先に実行される //出力結果 Start End 一秒後に表示
コールバック関数
以下は画像を先読みする関数 loadimage() の例で、生成した img 要素の src 属性に画像のパスを設定した後に、その画像の高さと幅を取得して出力する記述があります。
function loadimage(src) { //img 要素を生成 const img = document.createElement('img'); //img 要素の src 属性に引数で指定されたパスを設定(非同期的に行われる) img.src = src; //画像の高さと幅を取得して出力 console.log(`height:${img.height} / width:${img.width}`); } //上記関数を実行 loadimage('images/01.jpg'); //出力結果(高さと幅を取得できていない) //height:0 / width:0
上記を実行すると画像の高さと幅を取得することができません(画像がすでにキャッシュされていれば取得されます)。
これは img.src に画像のパスが指定されて読み込みが開始されますが、Image オブジェクトの読み込み(デコード)処理は非同期的に行われるため、まだ読み込みが完了していない時点で画像の高さと幅の取得が実行されてしまうためです。
この問題を解決するにはイベントとそのコールバック関数を使用します。
以下は onload イベントを使って画像がロードされたら画像の高さと幅を出力するコールバック関数を指定する例です。
function loadimage(src) { const img = document.createElement('img'); img.src = src; //onload イベントで画像がロードされたらコールバック関数を実行 img.onload = () => console.log(`height:${img.height} / width:${img.width}`); } //上記関数を実行 loadimage('images/01.jpg'); //出力結果(高さと幅を取得できている) height:1200 / width:1800
以下は上記と同じことですが、第2引数にコールバック関数を受け取るように書き換えた例です。
function loadimage(src, callback) { const img = document.createElement('img'); img.src = src; img.onload = () => callback(img); } //第2引数にコールバック関数を指定して関数を実行 loadimage('images/01.jpg', function(img) { console.log(`height:${img.height} / width:${img.width}`); });
以下は8〜10行目のコールバック関数をアロー関数で記述した例です(同じこと)。
//コールバック関数をアロー関数で記述した場合の実行例 loadimage('images/01.jpg', (img) => { console.log(`height:${img.height} / width:${img.width}`); }); // この場合は引数の括弧やブロックは省略できるので以下のようにも記述できます loadimage('images/01.jpg', img => console.log(`height:${img.height} / width:${img.width}`));
以下のようにコールバック関数を別途定義して指定することもできます(同じこと)。
function loadimage(src, callback) { const img = document.createElement('img'); img.src = src; img.onload = () => callback(img); } //コールバック関数を別途定義 function logSizes(img) { console.log(`height:${img.height} / width:${img.width}`); } //第2引数にコールバック関数を指定して関数を実行 loadimage('images/01.jpg',logSizes);
例外処理(Error-first callbacks)
前述の例はエラー(例外)を考慮していませんが、以下はコールバックを使ってエラー(例外)が発生した場合に対応する例です。
以下の関数 loadimage() は画像の読み込みに成功した場合は callback(null, img) を呼び、エラーが発生した場合は callback(new Error(`Image Not Found "${src}".`)) を呼びます。
画像の読み込みに成功した場合は、コールバック関数の1番目の引数に null、2番目の引数に読み込んだ画像を渡します。
画像の読み込みに失敗した(エラーが発生した)場合は、コールバック関数の1番目の引数に Error オブジェクトが渡されます。
コールバック関数では受け取った1番目の引数の値により処理を分岐します。
エラーが発生した場合は引数 error に Error オブジェクトが渡されるため error は真になるので、console.log(error)が実行されます。
画像の読み込みに成功した場合は error に null が渡されるので console.log(`height:${img.height} / width:${img.width}`) が実行されます。
function loadimage(src, callback) { const img = document.createElement('img'); img.src = src; // 画像がロードされた場合(onload イベント) // コールバック関数の第1引数に null、第2引数に読み込んだ画像を指定 img.onload = () => callback(null, img); // エラーが発生した場合(onerror イベント) // コールバック関数の第1引数に Error オブジェクトを生成して指定 img.onerror = () => callback(new Error(`Error: Image Not Found "${src}".`)); } //第2引数にコールバック関数を指定して関数 loadimage() を実行 loadimage('images/01.jpg', (error, img) => { // エラーが発生した場合 // 9行目で引数 error に Error オブジェクトが渡されるので error は true if (error) { // Error オブジェクトの message プロパティを出力 console.log(error.message); } else { // 画像がロードされた場合 // 6行目で引数 error に null が渡されるので error は false // 画像の高さと幅を出力 console.log(`height:${img.height} / width:${img.width}`); } }); //指定した画像が問題なくロードされた場合の出力例 //height:1200 / width:1800 //指定した画像が存在せず、エラーになった場合の出力 //Error: Image Not Found "images/02.jpg".
上記は Promise が導入されるまで非同期処理中に発生した例外を扱うパターンとして広く使われているもので Error-first callbacks と呼ばれ、以下のような決まりになっています。
- 処理失敗時:コールバック関数の1番目の引数にエラーオブジェクトを渡して呼び出す(Error-first)
- 処理成功時:コールバック関数の1番目の引数には null を渡し、2番目以降の引数に成功時の結果を渡して呼び出す
この決まりにより、コールバック関数では渡される1番目の引数を判定して失敗した場合と成功した場合の処理を分岐して対応することができます。
このパターンは Node.js でもよく使われています(Node.js:Error-first callbacks)。
同期処理と非同期処理の順序
同期的に実行中の処理に非同期処理が割り込むことはありません。
以下の場合、非同期処理の setTimeout で100ミリ秒後にメッセージを出力するようにタイマーを設定し、同期的な処理として500ミリ秒後にカウントを出力するようにしています。
タイミングとしては同期的な処理の途中でタイマーが完了しますが、同期的な処理を中断してタイマーのコールバック関数(メッセージの出力)が呼び出されることはないため、同期的な処理のカウントが先に出力されます。
このように、同期的にブロックする処理によって非同期のタスクの実行も後ろにずれてしまいます。
//非同期処理(100ミリ秒後にメッセージを出力) setTimeout(()=> { console.log("100ms 後に表示"); }, 100); //同期処理(500ミリ秒後にカウントを出力) const start1 = Date.now(); let count = 0; while (Date.now() - start1 < 500) { count ++; } console.log(`count: ${count}`); /* 出力例 count: 11913527 100ms 後に表示 */
例えば、以下のように同期的な処理と非同期処理が交互にある場合、同期的な処理が全て終了してから非同期処理が実行されます。
// 指定されたミリ秒の間カウントアップして同期的にブロックする関数 const blockByCount = (ms) => { const start = Date.now(); let count = 0; while (Date.now() - start < ms) { count ++; } console.log(`count: ${count}`); } // 指定されたミリ秒後にメッセージを表示する非同期処理の関数 const asyncMsg = (delay) => { setTimeout(()=> { console.log(`${delay}ms 後に表示(非同期)`); }, delay); } blockByCount(500); //同期的にブロックする処理 asyncMsg(100); // 非同期処理 blockByCount(300); //同期的にブロックする処理 asyncMsg(50); // 非同期処理 blockByCount(200); //同期的にブロックする処理 asyncMsg(10); // 非同期処理 /* 出力例 count: 11913868 count: 7179019 count: 4858700 100ms 後に表示(非同期) 50ms 後に表示(非同期) 10ms 後に表示(非同期) */
Promise を使ってみる
前述の非同期処理における例外処理(Error-first callbacks)は単なる慣例であって、仕様ではないためいろいろな書き方ができてしまいます。また、ネストが深くなるとコードの見通しが悪くなり、変更などをする場合のコストが高くなってしまいます。
そのため、JavaScript ES6(ECMAScript 2015/ES2015)で Promise という非同期処理を扱うための組み込みオブジェクトが導入されました。
Promise は非同期処理を扱うための Promise オブジェクトとその仕組み(機能)のことをいいます。
Promise を使うには非同期処理を行う関数を定義し、その関数で Promise オブジェクトのインスタンスを生成して返します。そして生成される Promise インスタンスの then メソッドに必要な処理を登録します。
Promise はチェインによってフロー制御をし、then メソッドをつなげていくことで、1つの非同期処理が終了したら次の非同期処理というように一連の非同期処理を簡単に書くことができます。
また、catch メソッドを使うことで包括的な例外(エラー)処理もできるようになっています。
Promise の大まかな仕組み
非同期処理を行う関数を定義し、その関数で Promise オブジェクトのインスタンスを生成して返します。
Promise のインスタンスは new
演算子とコンストラクタ Promise(executor)
で生成します。
Promise(executor)
の引数 executor
は2つの引数 (resolve, reject)
を受け取る関数で、通常以下のように無名関数として指定します。
(resolve, reject) => { /* 何らかの非同期処理 */ } // または function (resolve, reject) { /* 何らかの非同期処理 */ }
この関数の中で必要な処理を記述し、処理が成功した場合は第1引数の resolve
を呼び出し、結果の値 value
を引数に指定します。
失敗した場合は第2引数の reject
を呼び出し、エラーオブジェクト(error)を引数に指定します。
定義した関数を実行して Promise オブジェクトのインスタンスを生成し、その then()
メソッドに非同期処理が成功した場合と失敗した場合のコールバック関数を登録します。
これにより、何らかの非同期処理が成功した場合は then()
メソッドの第1引数に登録したコールバック関数が呼び出され、引数として resolve()
に指定した値 value
が渡されます(resolve します)。
失敗した場合は then()
メソッドの第2引数に登録した関数が呼び出され、引数として reject()
に指定した error が渡されます(reject します)。
//非同期処理を行う関数(Promise を返す関数)を定義 function myPromise(args) { //Promise を生成して返す return new Promise( (resolve, reject) => { // 何らかの非同期処理 // value : 処理による結果の値(コールバック関数に渡したい値) /* 成功した場合 */ //結果の値(value)を引数に指定して第1引数の resolve を呼び出す resolve(value); /* 失敗した場合 */ //エラーオブジェクト(error)を引数に指定して第2引数の reject を呼び出す reject(error); }); } //上記関数を実行して Promise オブジェクトを生成 const promise = myPromise(args); //then() メソッドに非同期処理が成功した場合と失敗した場合のコールバック関数を登録 (定義) promise.then( //第1引数(成功時に value を受け取るコールバック関数) (value) => { /* 成功した場合の処理 */ }, //第2引数(失敗時に error を受け取るコールバック関数) (error) => { /* 失敗した場合の処理 */ } );
上記では一度関数を実行して Promise を生成して変数に代入し(20行目)、23行目で then() メソッドを実行していますが、Promise の生成に続けて then() メソッドをチェーンすることもできます。
//Promise を返す関数を定義 function myPromise(args) { //Promise を生成して返す return new Promise( (resolve, reject) => { // 何らかの非同期処理 /* 成功した場合 */ resolve(value); /* 失敗した場合 */ reject(error); }); } //上記関数を実行して Promise を生成し、続けて then() メソッドを実行(チェーン) myPromise(args).then( (value) => { /* 成功した場合の処理 */ }, (error) => { /* 失敗した場合の処理 */ } );
また、then() メソッドの引数に成功した場合のコールバック関数を1つだけ登録し、失敗した場合のコールバック関数は catch()
を使って登録することもできます。
//Promise を返す関数を定義 function myPromise(args) { //Promise を生成して返す return new Promise( (resolve, reject) => { // 何らかの非同期処理 /* 成功した場合 */ resolve(value); /* 失敗した場合 */ reject(error); }); } //上記関数を実行して Promise オブジェクトを生成 const promise = myPromise(args); // then() メソッドに非同期処理が成功した場合のコールバック関数を登録 promise.then( // 成功時に value を受け取るコールバック関数 (value) => { /* 成功した場合の処理 */ } ).catch( // 失敗時に error を受け取るコールバック関数 (error) => { /* 失敗した場合の処理 */ } );
以下は前述の画像先読みの例外処理のコードです(Promise を使わない非同期処理の例)。
function loadimage(src, callback) { const img = document.createElement('img'); img.src = src; // 成功した場合 img.onload = () => callback(null, img); // 失敗した場合 img.onerror = () => callback(new Error(`Error: Image Not Found "${src}".`)); } //第2引数にコールバック関数を指定して関数 loadimage() を実行 loadimage('images/01.jpg', (error, img) => { if (error) { // 非同期処理が失敗したときの処理 console.log(error.message); } else { // 非同期処理が成功したときの処理 console.log(`height:${img.height} / width:${img.width}`); } });
以下は上記を promise を使って書き換えた例です。書き換えた関数 loadimage() はコールバック関数を受け取りません。代わりに Promise オブジェクトを生成して返します。
使用する側では loadimage() 関数を実行して Promise オブジェクトを生成します。そして then メソッドを使用してそのオブジェクトに対して成功と失敗時の処理をコールバック関数として登録します。
function loadimage(src) { //Promise オブジェクトを生成し返す return new Promise((resolve, reject) => { const img = document.createElement('img'); img.src = src; // 成功した場合:resolve を呼び出して結果(img)を引数に渡す img.onload = () => resolve(img); // 失敗した場合:reject を呼び出してエラーオブジェクトを引数に渡す img.onerror = () => reject(new Error(`Error: Image Not Found "${src}".`)); }); } //promise オブジェクトを生成 const promise = loadimage('images/01.jpg'); //then メソッドを使用 promise.then( // 非同期処理が成功したときの処理(コールバック)を登録 (img) => console.log(`height:${img.height} / width:${img.width}`), // 非同期処理が失敗したときの処理(コールバック)を登録 (error) => console.log(error.message) );
catch メソッドを使って16〜21行目の部分は以下のように記述することもできます。
/*** catch メソッドを使う場合 ***/ promise.then( // 非同期処理が成功したときの処理(コールバック)を登録 (img) => console.log(`height:${img.height} / width:${img.width}`) ).catch( // 非同期処理が失敗したときの処理(コールバック)を登録 (error) => console.log(error.message) );
生成した promise オブジェクトを変数に格納せずに、続けて then() を使用することもできます。こちらの書き方のほうがよく使われます。
function loadimage(src) { return new Promise((resolve, reject) => { const img = document.createElement('img'); img.src = src; img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Error: Image Not Found "${src}".`)); }); } //promise オブジェクトを生成して続けて then メソッドを使用 loadimage('images/01.jpg').then( (img) => console.log(`height:${img.height} / width:${img.width}`), (error) => console.log(error.message) );
以下は then() に成功した場合のコールバックを登録し、失敗した場合のコールバックは catch() に登録する例です(上記と同じこと)。
function loadimage(src) { return new Promise((resolve, reject) => { const img = document.createElement('img'); img.src = src; img.onload = () => resolve(img); img.onerror = () => reject(new Error(`Error: Image Not Found "${src}".`)); }); } // 失敗した場合のコールバックは catch() に登録する例 loadimage('images/01.jpg').then( (img) => console.log(`height:${img.height} / width:${img.width}`) ).catch( (error) => console.log(error.message) );
以下はアロー関数の代わりに関数リテラル(関数式)を使って記述した場合の例です。
function loadimage(src) { return new Promise(function(resolve, reject) { const img = document.createElement('img'); img.src = src; // 成功した場合(画像が正しくロードされた場合) img.onload = function() { //resolve を呼び出して結果(img)を引数に渡す resolve(img); }; // 失敗した場合(画像がロードできなかった場合) img.onerror = function() { //reject を呼び出してエラーオブジェクトを引数に渡す reject( new Error(`Error: Image Not Found "${src}".`) ); }; }); } loadimage('images/01.jpg').then( function(img) { console.log(`height:${img.height} / width:${img.width}`); }, function(error) { console.log(error.message); } );
以下は上記の18〜25行目を catch() を使って書き換えた場合です。
loadimage('images/01.jpg').then( function(img) { console.log(`height:${img.height} / width:${img.width}`); } ).catch( function(error) { console.log(error.message); } );
Promise 関数内の処理の順番
以下は Promise を返す関数内の処理の順番を確認する例です。
delayedHello は指定されたミリ秒後に Hello! という文字列を結果として持つ Promise を返す関数です。
Promise を返すという処理は同期的な処理なので、この関数を呼び出すと、まず2行目の「Promise 生成前」が即座に出力され、続いて4行目の「Promise 内:非同期処理の前」が即座に出力されます(この時点では Promise は解決していません)。そして17行目の同期的な処理の「呼び出し後」も即座に出力されます(これらはほぼ同時に出力されます)。
その後、関数の呼び出しから1000ミリ秒経過したら(Promise が解決したら)16行目の then メソッドにより Promise の結果の「Hello!」が出力されます。
function delayedHello(delay) { console.log('Promise 生成前'); return new Promise(function(resolve) { console.log('Promise 内:非同期処理の前'); // 非同期処理 setTimeout(() => { resolve('Hello!'); }, delay); }); } console.log('呼び出し前'); // Promise を作成して返す const promise = delayedHello(1000); // 非同期処理が完了したらその値を出力 promise.then((value)=> console.log(value)); console.log('呼び出し後'); /* 呼び出し前 Promise 生成前 Promise 内:非同期処理の前 呼び出し後 Hello! */
Promise インスタンスの生成
Promise は new 演算子で Promise オブジェクトのインスタンスを作成して利用します。
以下は Promise オブジェクトのコンストラクタの構文で、executor と呼ばれる関数を渡して Promise オブジェクトを生成します。
var promise = new Promise(executor)
executor は引数に resolve と reject を取り、その中で何らかの非同期処理を定義します。通常以下のように無名関数として、コンストラクタの引数に指定します。
executor の引数に受け取る resolve と reject はいずれも関数で、実際には好きな名前を付けることができます(関数内で呼び出す際には、その指定した名前で呼び出します)。
var promise = new Promise( function( resolve, reject ) { // 非同期処理(executor の処理)を記述 });;
//アロー関数での記述(上記と同じこと) var promise = new Promise( (resolve, reject) => { // 非同期処理(executor の内容)を記述 });
コンストラクタの引数に渡す関数 executor では何らかの処理を行い、その処理結果により executor の引数で受け取った関数(resolve, reject)を呼び出します。
- 処理が成功した場合→第1引数の resolve() を呼び出す(引数には結果の値を指定)
- 処理が失敗した場合→第2引数の reject() を呼び出す(引数には Error オブジェクトを指定)
var promise = new Promise( (resolve, reject) => { // 何らかの非同期処理 // 処理による結果の値(コールバック関数に渡したい値):value /* 成功した場合 */ //結果の値を引数に指定して第1引数の resolve を呼び出す resolve(value); /* 失敗した場合 */ //エラーオブジェクトを引数に指定して第2引数の reject を呼び出す reject(Error オブジェクト); });
非同期処理が成功した場合のみを扱う場合は resolve() 関数のみを呼び出し、非同期処理が失敗した場合のみを扱う場合は reject() 関数のみを呼び出すこともできます。
var promise = new Promise((resolve) => { // 何らかの非同期処理 /* 成功した場合のみを扱う */ //結果の値を引数に指定して第1引数の resolve を呼び出す resolve(value); });
成功した場合のみを扱う場合は、executor の第2引数を省略できますが、失敗した場合のみを扱う場合は第1引数(以下の場合は resolve)は省略できません。
var promise = new Promise((resolve, reject) => { // 何らかの非同期処理 /* 失敗した場合のみを扱う */ //エラーオブジェクトを引数に指定して第2引数の reject を呼び出す reject(Error オブジェクト); });
executor はそれぞれの処理結果に応じて resolve または reject を1つだけ呼びだす必要があります。2つ以上呼び出しても2つ目以降は無視されます。
- 成功した場合に対して1つの resolve
- 失敗した場合に対して1つの reject
また、resolve() や reject() に2つ以上の引数を渡した場合、最初の引数のみが使われ、以降の引数は無視されます。
Promise オブジェクトのインスタンスが生成されると自動的に executor(関数)が呼び出されます。また、コンストラクタが promise を生成する際に、対応する resolve と reject の関数のペアも生成します。
Promise の状態
生成された promise インスタンスは API からは直接操作できない内部のプロパティ(promise の状態を表す PromiseState と promise の結果を表す PromiseResult)を持っています。
promise の状態を表す PromiseState には pending、fullfilled、rejected の3つの状態があります。
以下のコードを実行すると、pending 状態、fullfilled 状態、rejected 状態の promise のインスタンスが生成され、コンソールにはそれぞれの PromiseState と PromiseResult が出力されます。
const pendingPromise = new Promise((resolve, reject) => { // resolve() も reject() を呼び出していないので pending 状態 }); console.log(pendingPromise); // Promise {<pending>} const fulfilledPromise = new Promise((resolve, reject) => { resolve('結果の値'); // resolve() を呼び出しているので fullfilled 状態 }); console.log(fulfilledPromise); // Promise {<fulfilled>: '結果の値'} const rejectedPromise = new Promise((resolve, reject) => { reject(new Error('エラー')); // reject() を呼び出しているので rejected 状態 }); console.log(rejectedPromise); // Promise {<rejected>: Error: エラー}
以下は上記を実行した際のコンソールタブのスクリーンショットです。rejectedPromise では例外を捕捉していないのでエラーになっていますが、then() メソッドの第2引数や catch() メソッドで例外を捕捉することができます。
PromiseState | 説明 | PromiseResult |
---|---|---|
pending | new Promise でインスタンスを生成した時の状態(初期状態) | undefined |
fulfilled | 処理が成功して完了したこと(resolve)を意味し、何らかの処理結果の値(value)を持っています | value |
rejected | 処理が失敗したこと(reject)を意味し、失敗の理由を表す値(error:多くの場合は Error のインスタンス)を持っています | error |
非同期処理を行う関数はその処理に対応した Promise を作成して返します。
Promise を返すという処理は同期的な処理なので、その時点では Promise はまだ非同期処理の結果が決まっていないため Promise の状態は pending で、Promise の結果は undefined です。
その後、非同期処理が完了した時点で、非同期処理の結果に応じて状態と結果が変わります。
つまり、生成時に pending 状態の Promise は非同期処理の結果により、fulfilled または rejected のいずれかの終了状態に変わり、then メソッドによって関連付けられたコールバック関数が呼ばれ、対応する Promise の結果が渡されます。
promise オブジェクトの状態は、一度初期状態の Pending から fulfilled や rejected の終了状態に遷移すると、その promise オブジェクトの状態はそれ以降変化することはありません。
Event 等とは異なり、promise の状態の変化は最終的なものです。
言い換えると、executor により行われた処理は1つの結果(value または error)のみを持つということになります。
そして、処理の結果(値またはエラー)は then() メソッドに登録した関数で受け取ることができます。
Settled
Promise インスタンスの状態は生成時は Pending で、その後 fulfilled(成功した場合)または rejected(失敗した場合)へ変化し、それ以降状態は変化しません。 このため fulfilled または rejected の状態であることを Settled と表現します。
英語の Settled には「変化がなく安定した」や「確定した」という意味があります。
then メソッド
promise オブジェクトには promise の状態が遷移した時(成功または失敗した際)に呼ばれるコールバック関数を登録するための promise.then() というインスタンスメソッドがあります。
then() メソッドに関数を登録(定義)することで、処理が成功した場合や失敗した場合にその登録した関数が呼び出されて実行されます。
then() メソッドは、引数に成功した場合のコールバック関数(onFulfilled)と失敗した場合のコールバック(onRejected)を受け取ります。いずれも省略可能で、成功した場合か失敗した場合いずれかのコールバックのみを登録することもできます。以下が構文です。
promise.then(onFulfilled, onRejected)
以下のように直接関数を定義することもできます。
promise.then( function(value) { /* 成功した場合の処理 */ }, //onFulfilled(第1引数) function(error) { /* 失敗した場合の処理 */ } //onRejected(第2引数) ); //アロー関数を使って記述する場合 promise.then( (value) => { /* 成功した場合の処理 */ }, //onFulfilled(第1引数) (error) => { /* 失敗した場合の処理 */ } //onRejected(第2引数) );
- onFulfilled コールバック(1つ目の引数)
- 非同期処理が成功/解決(resolve)したときに実行され、その結果(value)を受け取る関数
- onRejected コールバック(2つ目の引数)
- 非同期処理が失敗/拒否(reject)したときに実行され、エラー(error)を受け取る関数
以下は非同期処理が成功した場合の流れになります。
Promise のコンストラクタに渡した executor には非同期処理が完了した際に実行されるコールバック関数(resolve, reject)が定義されています。
- 非同期の処理が成功すると処理結果 value が1つ目のコールバック resolve() に渡され呼び出されます
- value(結果の値)は promise インスタンスに戻されます
- promise インスタンスは then() メソッドを実行します
- value は then() に登録した onFulfilled の引数に渡されて実行されます
以下の random_results() は Promise のインスタンスを生成して返す関数です。
random_results() は引数に受け取った数値 num と生成した乱数 rand が一致した場合(成功した場合)は、引数に生成した乱数の値(結果)を渡して resolve() を呼び出します。
値が一致しない場合(失敗した場合)は引数に Error オブジェクトを渡して reject() を呼び出します。
成功した場合の resolve() に渡した「結果の値」 rand は、then メソッドの第1引数のコールバック(onFulfilled)に引数 result として渡されます(24行目)。
失敗した場合の reject() に渡した Error オブジェクトは、then メソッドの第2引数のコールバック(onRejected)に引数 error として渡されます(26行目)。
function random_results(num) { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //成功した場合(乱数と一致した) if (rand === num) { //resolve() の引数に指定した値は、成功したときのコールバック(24行目)に渡される resolve(rand); } else { //失敗した場合(乱数と一致しない) // エラーオブジェクトを引数に指定 reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //random_results() を呼び出して promise オブジェクトを生成 const promise = random_results(4); //then() メソッドで処理結果(成功・失敗)により実行する処理(コールバック)を登録 promise.then( // 非同期処理が成功したときのコールバック onFulfilled(第1引数) (result) => console.log(`大当たり!:${result}`), // 非同期処理が失敗したときのコールバック onRejected(第2引数) (error) => console.log(error.message) ); /* 実行例 大当たり!:4 //成功時 残念でした。値:8 //失敗時 */
以下のように promise オブジェクトを生成して続けて then() メソッドを記述することもできます。
function random_results(num) { return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { if (rand === num) { resolve(rand); } else { reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //random_results() を呼び出して promise オブジェクトを生成して then() を実行 random_results(4).then( (result) => console.log(`大当たり!:${result}`), (error) => console.log(error.message) );
成功した場合のコールバック関数だけを登録
処理が成功した場合だけを扱いたい場合は、then() に1つの引数だけを指定することもできます。
以下は promise インスタンスに対して then メソッドで成功時のコールバック関数だけを登録する例です。
function random_results(num) { //Promise のインスタンスを作成して返す(処理が成功した場合だけを扱う例) return new Promise((resolve) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //成功した場合(乱数と一致した) if (rand === num) { //以下の引数に指定した値は、成功したときのコールバック(20行目)の引数として渡される resolve(rand); } }, 100 ); }); } //random_results() を呼び出して promise オブジェクトを生成 const promise = random_results(4); //then() メソッドで成功の場合に実行する処理を登録 promise.then( // 非同期処理が成功したときのコールバック(第1引数) (result) => console.log(`大当たり!:${result}`), );
失敗した場合のコールバック関数だけを登録
処理が失敗した場合のコールバック関数だけを登録することもできます。 この場合は、then(undefined, onRejected)のように第1引数には undefined を渡す必要があります。
また、catch メソッドを使って catch(onRejected) としても then(undefined, onRejected) と同じことができます。
function random_results(num) { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //失敗した場合(乱数と一致しない) if (rand !== num) { //引数にはエラーオブジェクトを渡し、失敗したときのコールバック(21行目)に渡される reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = random_results(4); //第1引数には undefined を渡すことにより失敗した時のコールバックが呼び出される promise.then( undefined, //第1引数 //非同期処理が失敗したときのコールバック(第2引数) (error) => console.log(error.message) ); //以下のように catch メソッドを使っても上記の then(undefined, onRejected) と同じこと promise.catch( //非同期処理が失敗したときのコールバック (error) => console.log(error.message) );
catch メソッド
promise.catch() は promise オブジェクトが rejected の状態になった時(処理の失敗の場合)に呼ばれる関数を登録するためのインスタンスメソッドです。
また、catch メソッドの実態は then(undefined, onRejected) のシンタックスシュガー(簡単な記述方法)です(失敗した場合のコールバック関数だけを登録)。
then() に1つの引数だけを指定して成功した場合のコールバック関数だけを登録し、失敗した場合のコールバック関数は catch() を使って登録することもできます。
function random_results(num) { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //成功した場合(乱数と一致した) if (rand === num) { //引数に指定した値(rand)は、成功したときのコールバック(24行目)に引数として渡される resolve(rand); } else { //失敗した場合(乱数と一致しない) // エラーオブジェクトを引数に指定 reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = random_results(4); //成功時のコールバックは then で登録し、失敗時のコールバックを catch で登録 promise.then( // 非同期処理が成功したときのコールバック (result) => console.log(`大当たり!:${result}`) ).catch( // 非同期処理が失敗したときのコールバック (error) => console.log(error.message) );
関連項目:失敗時の処理 catch メソッド
finally メソッド
promise.finally() は処理の成功時/失敗時のどちらの場合でも呼び出されるコールバック関数を登録するためのインスタンスメソッドで、ECMAScript 2018 で導入されました。
finally() を使えば、処理の成功・失敗に関係なく必要なコードを実行することができます。
以下のような特徴があります。
- finally() は引数を取りません
- finally() は次のハンドラに結果(の値)またはエラーを渡します
function random_results(num) { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //成功した場合(乱数と一致した) if (rand === num) { //引数に指定した値(rand)は、成功したときのコールバック(23行目)に引数として渡される resolve(rand); } else { //失敗した場合(乱数と一致しない) // エラーオブジェクトを引数に指定 reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = random_results(4); promise.then( // 非同期処理が成功したときのコールバック (result) => console.log(`大当たり!:${result}`) ).catch( // 非同期処理が失敗したときのコールバックを catch で登録 (error) => console.log(error.message) ).finally( //成功でも失敗でも呼び出されるコールバック ()=> console.log('Finally!') ) /* 実行例 //成功時 大当たり!:4 Finally! //失敗時 残念でした。値:8 Finally! */
finally() は次のハンドラに結果あるいはエラーを渡します。以下の例では結果の result(resolve の引数に渡された rand)が finally から then へと渡されています(21〜26行目)。
function random_results(num) { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //成功した場合(乱数と一致した) if (rand === num) { //引数に指定した値は、成功したときのコールバックに引数として渡される resolve(rand); } else { //失敗した場合(乱数と一致しない) // エラーオブジェクトを引数に指定 reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = random_results(4); promise.finally( //成功でも失敗でも呼び出されるコールバック ()=> console.log('Start!') ).then( // 非同期処理が成功したときのコールバック (result) => console.log(`大当たり!:${result}`) ).catch( // 非同期処理が失敗したときのコールバックを catch で登録 (error) => console.log(error.message) ).finally( //成功でも失敗でも呼び出されるコールバック ()=> console.log('End!') ) /* 実行例 //成功時 Start! 大当たり!:4 End! //失敗時 Start! 残念でした。値:8 End! */
Promise と 例外
Promise ではコンストラクタのコールバック関数(executor)内での処理で例外が発生した(エラーがスローされた)場合、自動的に例外が捕捉(キャッチ)されます。
Promise の処理で例外(エラー)が発生すると、失敗して reject() を呼び出したのと同じように then メソッドの第2引数や catch メソッドで登録した失敗の場合のコールバック関数が呼び出されます。
そしてそれ以降のコンストラクタの処理は実行されません。
また、then や catch のコールバック関数内で発生した例外も自動的に捕捉(キャッチ)されます。
以下は7行目で throw new Error() を記述してわざと例外を発生させた例で、それ以降の処理はされず「失敗のコールバック:例外発生!」とコンソールに出力されます。
function random_results(num) { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); //コンストラクタ内で発生した例外は自動的に捕捉され reject() を呼び出しエラーが渡されます throw new Error("例外発生!"); // 例外が発生すると以降の処理は実行されません setTimeout(() => { if (rand === num) { resolve(rand); } else { reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = random_results(4); promise.then( // 非同期処理が成功したときのコールバック (result) => console.log(`大当たり!:${result}`), // 非同期処理が失敗したときのコールバック (error) => console.log('失敗のコールバック:' + error.message) ); /* 実行例 例外発生! */
但し、Promise の中の非同期処理内(この例の場合は setTimeout の中)で発生したエラーは補足できず、エラーになってしまいます。
function random_results(num) { return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { // ここでのエラーは捕捉されない throw new Error("例外発生(補足されない!)"); if (rand === num) { resolve(rand); } else { reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } const promise = random_results(4); promise.then( (result) => console.log(`大当たり!:${result}`), (error) => console.log('失敗のコールバック:' + error.message) ); console.log('Hello!'); /* 実行例 Hello! Uncaught Error: 例外発生(補足されない!)// 補足できず、エラーになる */
必要に応じて非同期処理内(この例の場合は setTimeout の中)で try~catch で囲んでエラーを補足し、catch() で reject を呼び出します。
function random_results(num) { return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { // 非同期処理内で try~catch で囲んでエラーを補足 try { // 何らかの処理 throw new Error('捕捉されるエラー'); } catch(err) { reject(err); // reject を呼び出す } if (rand === num) { resolve(rand); } else { reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } const promise = random_results(4); promise.then( (result) => console.log(`大当たり!:${result}`), (error) => console.log('失敗のコールバック:' + error.message) ); console.log('Hello!'); /* 実行例 Hello! 失敗のコールバック:捕捉されるエラー */
Promise.resolve
Promise.resolve() は fulfilled の状態となった Promise インスタンスを返す静的メソッドで、渡された任意の値に即時に解決(resolve)される Promise を作成します。
通常 executor は非同期で何らかの処理を行い、暫く時間が経過した後に resolve(や reject)を呼び出しますが、この場合はすぐに resolve が呼び出されます。
const promise = Promise.resolve();
Promise.resolve() は以下のシンタックスシュガー(簡略化した記述方法)です。
const promise = new Promise((resolve) => { resolve(); }); //または、以下でも同じ(引数と式の括弧を省略) const promise = new Promise(resolve => resolve());
引数に値を渡すこともできます。
const promise = Promise.resolve('hello'); //上記は以下と同じこと const promise = new Promise((resolve) => { resolve('hello'); }); promise.then( value => console.log(value) ); // hello と出力 //引数に値を渡さずに呼び出すと undefined で実行される Promise が作成されます const promise = Promise.resolve(); promise.then( value => console.log(value) ); // undefined と出力
既に fulfilled の状態に遷移した Promise インスタンスの(then メソッドで登録した)コールバック関数の呼び出しは非同期なタイミングで実行されます。
以下を実行すると、同期的な処理が実行された後に、then メソッドのコールバック関数が実行されることが確認できます。
const promise = Promise.resolve('hello'); //then メソッドで登録したコールバック関数の呼び出し promise.then((val) => { console.log(val); // hello }); //同期的な処理 console.log('こんにちは'); //同期的な処理 console.log('ハロー'); //出力 /* こんにちは ハロー hello */
Promise.reject
Promise.reject() は rejected の(状態となった) Promise インスタンスを返す静的メソッドです。
引数には Error オブジェクトを渡します。
const promise = Promise.reject(new Error('Error!'));
Promise.reject() は以下のシンタックスシュガー(簡単な記述方法)です。
const promise = new Promise((resolve, reject) => { reject(new Error('Error!')); }); //または、以下でも同じ const promise = new Promise((resolve, reject) => reject(new Error('Error!')));
Promise.reject() で作成した rejected の状態の Promise インスタンスに対しても then や catch メソッドでコールバック関数を登録することができます。
const promise = Promise.reject(new Error('Error!')); promise.catch((error) => { console.log(error.message); //Error! }); // then メソッドを使った記述(上記と同じこと) promise.then( undefined, (error) => { console.log(error.message); //Error! }); //error を使わない例 promise.catch(() => { console.log('エラー!'); //エラー! });
既に rejected の状態に遷移した Promise インスタンスの(catch や then メソッドで登録した)コールバック関数の呼び出しは、fulfilled の場合と同様、非同期なタイミングで実行されます。
以下を実行すると、同期的な処理が実行された後に、catch メソッドのコールバック関数が実行されることが確認できます。
//catch メソッドで登録したコールバック関数の呼び出し Promise.reject(new Error('Error!')).catch((error) => { console.log(error.message); //Error! }); //同期的な処理 console.log('こんにちは'); //同期的な処理 console.log('ハロー'); //出力 /* こんにちは ハロー Error! */
Promise チェーン
then メソッドと catch メソッドは常に新しい Promise インスタンスを返します。
そのため、then メソッドの返り値である Promise インスタンスにさらに then メソッドで処理を登録することで、これらをチェーン (連鎖) させることができます。
Promise チェーンでは、Promise の処理が失敗して rejected 状態にならない限り、then メソッドで登録した成功時のコールバック関数が順番に呼び出されます。
また、任意の値(結果)を返すことで、値(結果)を then メソッドのチェーン (連鎖)を通じて渡すことができます。 then メソッドで値が返されるとき、その promise は解決され、次の then メソッドはその値で実行されます。
言い換えると、then メソッドのコールバック関数が返した値は、次の then メソッドのコールバック関数へ引数として渡されます。
以下は then メソッドを使った Promise チェーンの例です。
new Promise(function(resolve, reject) { setTimeout(() => resolve('hello'), 1000); //1秒で解決される }).then(function(result) { console.log(result); // hello return result + ' hello2'; //結果を(処理して)返す }).then(function(result) { //8行目で return された値が result に入っている console.log(result); // hello hello2 return result + ' hello3'; //結果を(処理して)返す }).then(function(result) { //13行目で return された値が result に入っている console.log(result); // hello hello2 hello3 }); /* 出力結果 hello hello hello2 hello hello2 hello3 */
上記のコードは以下のような流れになります。
- 最初の promise は1秒で解決(resolve)されます
- 最初の then メソッドが呼ばれ hello が出力されます
- 返された値(result + ' hello2')は次の then メソッドへ渡されます
- 2つ目の then メソッドが呼ばれ hello hello2 が出力されます
- 返された値(result + ' hello3')は次の then メソッドへ渡されます
- 3つ目の then メソッドが呼ばれ hello hello2 hello3 が出力されます
最初の promise は1秒で解決され、最初の then メソッドが呼ばれ hello が出力されます。そして直ちに次のメソッドが呼ばれて hello hello2 が出力され、続いて hello hello2 hello3 が出力されます(実際には1秒後に順番通りにほぼ同時に出力されます)。
promise を返す(非同期処理のチェーン)
通常、then メソッドにより返された値は直ちに次の then メソッドに渡されますが、新たに promise が生成されて返された場合はそれ以降の処理はその promise が解決する(処理が完了する)まで待ちます。そして処理が完了すると promise の結果を取得して、次の then メソッドに渡されます。
以下の場合、最初の then メソッドでは3行目の setTimeout により1秒後に hello が出力され、new Promise(...) で promise を返します。1秒後にそれは解決され(処理が完了し)結果(resolve の引数) は2番目の then メソッドに渡されます。そして同じことが繰り返されます。
出力される内容は前述の例と同じですが、以下の例の場合はそれぞれの出力の間に1秒の間隔(遅延)があります。
promise を返すことで非同期処理のチェーンを組み立てることができます。
new Promise(function(resolve, reject) { setTimeout(() => resolve('hello'), 1000); //1秒で解決される }).then(function(result) { //成功時の処理 console.log(result); // hello(1秒後) //promise を返す return new Promise((resolve, reject) => { setTimeout(() => resolve(result + ' hello2'), 1000); }); }).then(function(result) { //成功時の処理 console.log(result); // hello hello2(さらに1秒後) //promise を返す return new Promise((resolve, reject) => { setTimeout(() => resolve(result + ' hello3'), 1000); }); }).then(function(result) { //成功時の処理 console.log(result); // hello hello2 hello3(さらに1秒後) }); /* 出力結果 hello //開始から1秒後 hello hello2 //開始から2秒後 hello hello2 hello3 //開始から3秒後 */
上記は以下のように書き換えられます。
let count = 1; function hello(val) { return new Promise(function(resolve) { let hello = count > 1 ? val + ' hello' + count : 'hello'; count++; setTimeout(() => resolve(hello), 1000); }); } //promise オブジェクトを生成 const promise = hello( ); //then メソッドを実行 promise.then( result => { //成功時の処理 console.log(result); //hello() に結果を渡して Promise を返す return hello(result); } ).then( result => { //成功時の処理 console.log(result); // Promise を返す return hello(result); } ).then( result => { //成功時の処理 console.log(result); // Promise を返す return hello(result); } )
失敗時の処理 catch メソッド
処理が成功(resolve)した場合は then メソッドの第1引数で登録した成功時の処理だけが呼び出され、処理が失敗(reject)した場合は then メソッドの第2引数で登録した失敗時の処理または catch メソッドで登録した失敗時の処理だけが呼び出されます。
以下の is_random_even() は乱数を生成して、生成した乱数が偶数の場合は処理が成功(fulfilled)として resolve(rand) で乱数の値を成功時のコールバックに渡し、生成した乱数が奇数の場合は処理が失敗(rejected)とする Promise のインスタンスを作成して返す関数です。
以下は then メソッドで成功時の処理を記述し、catch メソッドで失敗時の処理を記述した非同期処理のチェーンの例です。
生成した乱数が奇数で処理が失敗した(Promise が rejected の状態になった)場合は、最も近い失敗時の処理が呼び出され、間にある成功時の処理はスキップされて実行されません。
function is_random_even() { //Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { //成功した場合(乱数が偶数の場合) if (rand%2 === 0) { //以下の引数に指定した値(rand)は成功したときのコールバックに result として渡される resolve(rand); } else { //失敗した場合(乱数が奇数の場合) //引数に指定したエラーオブジェクトは失敗したときのコールバックに error として渡される reject(new Error(`偶数ではありません。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = is_random_even(); promise.then( // 非同期処理が成功したときのコールバック result => { console.log(`1回目の偶数です。:${result}`); // Promise を返す return is_random_even(); } ).then( // 非同期処理が成功したときのコールバック result => { console.log(`2回連続の偶数です。:${result}`); // Promise を返す return is_random_even(); } ).then( // 非同期処理が成功したときのコールバック result => { console.log(`3回連続の偶数です。:${result}`); // Promise を返す return is_random_even(); } ).catch( // 非同期処理が失敗したときのコールバック (error) => console.log(error.message) ) /*出力例*/ //最初に奇数が出た場合(最初で失敗した場合) //catch で登録した失敗時の処理が呼び出され、成功時の処理は実行されない //偶数ではありません。値:5 //2回目に奇数が出た場合(2回目で失敗した場合) //成功時の処理が1回呼び出され、失敗時の処理が呼び出される //1回目の偶数です。:2 //偶数ではありません。値:3 //3回目に奇数が出た場合(3回目で失敗した場合) //成功時の処理が2回呼び出され、失敗時の処理が呼び出される //1回目の偶数です。:4 //2回連続の偶数です。:4 //偶数ではありません。値:3 //3回全てが偶数の場合(全て成功 ) //成功時の処理が3回呼び出され、失敗時の処理は呼び出されない //1回目の偶数です。:6 //2回連続の偶数です。:4 //3回連続の偶数です。:4
then メソッドや catch メソッドのコールバック関数内で発生した例外も自動的にキャッチされ、then メソッドや catch メソッドは rejected な状態の Promise インスタンスを返します。
そのため、例外が発生する場合も最も近くの失敗時の処理(catch または then の第2引数に登録したコールバック)が呼び出されます。
以下の場合、最初の then メソッドにより hello と出力されますが、14行目で例外が発生しているので、その後の処理はスキップされ、catch メソッドに登録された失敗時の処理が呼び出されます。
new Promise((resolve, reject) => { setTimeout(() => resolve('hello'), 1000); }).then((result) => { console.log(result); //hello return new Promise((resolve, reject) => { setTimeout(() => resolve(result + ' hello2'), 1000); }); }).then((result) => { throw new Error("例外発生!"); //例外を発生させる console.log(result); return new Promise((resolve, reject) => { setTimeout(() => resolve(result + ' hello3'), 1000); }); }).then((result) => console.log(result) ).catch( // 非同期処理が失敗したときのコールバック (error) => console.log(error.message) ); /* //出力 hello 例外発生! //console.log(error.message) */
Promise.all
Promise.all を使うと複数の Promise を使った非同期処理を同時(並列)に実行し、全ての Promise の処理が完了した時点で then メソッドのコールバック関数を呼び出します。
複数の Promise を使った非同期処理を1つの Promise として扱うことができます。以下が構文です。
const promise = Promise.all(iterable);
iterable は Promise の反復可能オブジェクト(配列など)を表します。多くの場合、iterable は Promise インスタンスの配列なので以下と同じです。
const promise = Promise.all( [ promiseInstance, ... ] );
Promise.all は Promise インスタンスの配列を受け取り、新しい Promise インスタンスを返します。
返り値の Promise は、配列の全ての Promise インスタンスの処理が解決(resolve)した場合に fulfilled の状態になり、もし1つでも処理が失敗(reject)した場合は、残った Promise の処理は実行せず、その時点で即座に返り値の Promise も rejected の状態になります(エラーと一緒に reject します)。
then メソッドで登録したコールバック関数が受け取る引数は全ての Promise インスタンスの結果をまとめた配列で、その要素の並び順は Promise.all メソッドに渡した Promise インスタンスの順番になります。
1つずつ順番に処理を実行したい場合は、Promise チェーンを利用します。
以下の場合、Promise.all は全ての処理が完了する2000ミリ秒後に解決し、then メソッドは全ての処理の結果の配列を受け取ります。
受け取る配列の要素の順序は Promise.all メソッドに渡した配列の順番になっています。
以下では1つ目の promise が解決まで最も時間がかかりますが、結果の配列の中では1つ目になります。
function delayed_action(num, ms) { return new Promise((resolve) => { setTimeout(() => { resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500); const promise3 = delayed_action(3, 1000); // Promise.all による新しい Promise インスタンスを作成 const promiseAll = Promise.all([promise1, promise2, promise3]); // 結果を出力 promiseAll.then(response => console.log(response)); /* 出力(全ての Promise インスタンスの結果をまとめた配列) ["1つ目の処理(2000 ミリ秒)", "2つ目の処理(500 ミリ秒)", "3つ目の処理(1000 ミリ秒)"] */ // forEach を使って出力 promiseAll.then(responses => responses.forEach( response => console.log(response) )); /* 出力 1つ目の処理(2000 ミリ秒) 2つ目の処理(500 ミリ秒) 3つ目の処理(1000 ミリ秒) */
Promise.all に渡す個々の Promise は変数に入れる必要はないので、以下のようにも記述できます。
以下では、then メソッドの引数の配列を分割代入を使って受け取っています。また、そのまま then メソッドでつなげることもできます。
function delayed_action(num, ms) { return new Promise((resolve) => { setTimeout(() => { resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } // Promise.all による新しい Promise インスタンスを作成 const promiseAll = Promise.all( [ delayed_action(1, 2000), delayed_action(2, 500), delayed_action(3, 1000) ] ); // 受け取る引数を分割代入 promiseAll.then(([promise1, promise2, promise3])=> { console.log(promise1); console.log(promise2); console.log(promise3); } ); /* 1つ目の処理(2000 ミリ秒) 2つ目の処理(500 ミリ秒) 3つ目の処理(1000 ミリ秒) */
いずれかの Promise インスタンスが reject された場合、Promise.all は即座に rejected になります。
以下の場合、2つ目のインスタンスが 0.5 秒後に reject され、即座に Promise.all の reject に繋がり catch メソッドが実行されます。 reject されたエラーは Promise.all 全体の結果になります。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } Promise.all( [ delayed_action(1, 2000), delayed_action(2, 500, true), // 失敗する Promise delayed_action(3, 1000) ] ) .then(response => console.log(response)) .catch( (error) => console.log(error.message) ); /* 以下が 0.5 秒後に出力される エラー:2つ目の処理(500 ミリ秒) */
Promise.allSettled
Promise.allSettled は Promise.all 同様、Promise インスタンスの配列(反復可能オブジェクト)を受け取り、新しい Promise インスタンスを返します。Promise.allSettled は ES2020 で追加されました。
const promise = Promise.allSettled( [ promiseInstance, ... ] );
Promise.all の場合は、渡されたいずれかの Promise が失敗すると新しい Promise も失敗になりますが、Promise.allSettled は渡された全ての Promise が解決(成功または失敗)するまで待ち、渡された個々の Promise の結果に関わらず成功します。
Promise.allSettled の返り値の結果は、Promise.all 同様、配列ですが、Promise.allSettled の場合、配列の各要素は以下のようなオブジェクトになります。
- 成功した場合:
{status:"fulfilled", value:結果の値}
- エラーの場合:
{status:"rejected", reason:結果の値}
各 Promise が成功したかどうかは status プロパティで判断します。
以下の場合、すべての Promise は成功し、status が "fulfilled" のオブジェクトの配列が返されます。
function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500); const promise3 = delayed_action(3, 1000); Promise.allSettled([promise1, promise2, promise3]) .then(response => console.log(response)); /* 以下が出力されます [ { "status": "fulfilled", "value": "1つ目の処理(2000 ミリ秒)" }, { "status": "fulfilled", "value": "2つ目の処理(500 ミリ秒)" }, { "status": "fulfilled", "value": "3つ目の処理(1000 ミリ秒)" } ] */
Promise.allSettled は、いずれかの Promise インスタンスが reject された場合でも全ての Promise の結果が出るまで待ちます。
その性質上、Promise.allSettled が返す新しい Promise は失敗することはなく、渡されたすべての Promise が拒否されても catch に移らないので、promise.allSettled に catch は不要です。
以下は2つ目の Promise が0.5 秒後に reject されますが、すべての Promise の結果が出てから新しい Promise が返されます。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500, true); // 失敗する Promise const promise3 = delayed_action(3, 1000); Promise.allSettled([promise1, promise2, promise3]) .then(response => console.log(response)); /* 以下が出力されます [ { "status": "fulfilled", "value": "1つ目の処理(2000 ミリ秒)" }, { "status": "rejected", "value": "エラー:2つ目の処理(500 ミリ秒)" }, { "status": "fulfilled", "value": "3つ目の処理(1000 ミリ秒)" } ] */
Promise.allSettled の結果には成功と失敗の両方が入る可能性があります。
成功と失敗を区別するには、各 Promise に対応して得られる結果のオブジェクトの status で区別することができます。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500, true); // 失敗する Promise const promise3 = delayed_action(3, 1000); Promise.allSettled([promise1, promise2, promise3]) .then(response => { // 成功した Promise の配列 const fulfilled = response.filter(data => data.status === 'fulfilled'); // 失敗した Promise の配列 const rejected = response.filter(data => data.status === 'rejected'); console.log(fulfilled); /* [ { "status": "fulfilled", "value": "1つ目の処理(2000 ミリ秒)" }, { "status": "fulfilled", "value": "3つ目の処理(1000 ミリ秒)" } ] */ console.log(rejected); /* [ { "status": "rejected", "value": "エラー:2つ目の処理(500 ミリ秒)" } ] */ });
Promise.race
Promise.race は Promise.all と同様、Promise インスタンスの配列を受け取り、新しい Promise インスタンスを返しますが、いずれか1つの Promise インスタンスの処理が完了した時点で、返された新しい Promise インスタンスは、配列の中で一番最初に完了した Promise インスタンスと同じ状態になります。
以下が構文です。
const promise = Promise.race([promiseInstance, ... ]);
最初に完了した処理の結果(失敗の場合はエラー)が新しい Promise インスタンス(Promise.race 全体)の結果になります。それ以外の Promise の結果やエラーは無視されます。
then メソッドのコールバック関数は一度しか呼び出されません。
以下の場合、Promise.race は promise2 の処理が完了する時点(500ミリ秒後)に確定し、then メソッドはその処理の結果を受け取ります。
function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`最初に完了:${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500); const promise3 = delayed_action(3, 1000); Promise.race([promise1, promise2, promise3]).then(value => console.log(value)); /* 出力 最初に完了:2つ目の処理(500 ミリ秒) */
以下は、配列に追加したエラーが発生する処理が最初に確定するので、全体の結果はエラーとなり catch メソッドのコールバックが呼ばれます。
function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`最初に完了:${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500); const promise3 = delayed_action(3, 1000); Promise.race([ promise1, //処理時間が一番短く且つエラーになる Promise インスタンスを追加 new Promise((resolve, reject) => setTimeout(() => reject(new Error('エラー発生!')), 10) ), promise2, promise3 ]).then( value => console.log(value) ).catch( error => // 失敗したときのコールバック console.log(error.message) ); /* 出力 エラー発生! */
以下の場合、エラーが発生する3つ目の処理(promise3)が実行される前に、2つ目の処理(promise2)が確定するのでエラーにはなりません。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`最初に完了:${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500); const promise3 = delayed_action(3, 1000, true); // 失敗する Promise Promise.race([promise1, promise2, promise3]).then(value => console.log(value)).catch( error => // 失敗したときのコールバック console.log(error.message) ); /* 出力 最初に完了:2つ目の処理(500 ミリ秒) */
以下の場合は、最初に完了する処理がエラーになるので、エラーが発生します。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`最初に完了:${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500, true); // 失敗する Promise const promise3 = delayed_action(3, 1000); Promise.race([promise1, promise2, promise3]).then(value => console.log(value)).catch( error => // 失敗したときのコールバック console.log(error.message) ); /* 出力 エラー:2つ目の処理(500 ミリ秒) */
Promise を使った非同期処理のタイムアウト
Promise.race を使うことで Promise を使った非同期処理のタイムアウト(一定時間経過しても処理が終わらない場合はエラーとして扱う処理)が実装できます。
以下は指定したミリ秒後に失敗する Promise を返す関数 delayedReject とランダムな時間後に成功する Promise を返す関数 delayed_action で作成した Promise のインスタンスを Promise.race に指定しています。
delayedReject で指定した時間内に delayed_action で作成した Promise のインスタンスの処理が完了しない場合は Promise.race で作成される Promise が失敗するのでタイムアウトとなります。
// 指定したミリ秒後に失敗する Promise を返す関数 function delayedReject(delay) { return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error(`タイムアウト:${delay}ミリ秒経過`)); }, delay); }); } // 乱数を使ったランダムな時間後に成功する Promise を返す関数 function delayed_action() { return new Promise((resolve) => { // 乱数を使ってランダムな遅延時間(ミリ秒単位)を作成 const randomDelay = Math.random() * 1000; // 上記で作成した遅延時間後に成功する setTimeout(() => { resolve(`完了:(${randomDelay} ミリ秒)`); }, randomDelay) }) } Promise.race([ delayed_action(), delayedReject(500), ]).then(response => { console.log(response); // 例:完了:(79.7... ミリ秒)※ 500ms以内で完了した場合 }).catch(error => { console.log(error.message); // タイムアウト:500ミリ秒経過 ※ 500ms経過した場合 });
但し、この場合、Promise.race で作成される Promise は delayedReject で指定した時間を経過すると失敗してエラーを返し終了しますが、delayed_action で作成した Promise のインスタンスの処理がその時点で終了するわけではなく、その処理は最後まで実行されます。
Promise.any
Promise.any は Promise.race 同様、Promise インスタンスの配列を受け取り、新しい Promise を返しますが、渡された Promise のいずれかが成功した時点で Promise.any の Promise も成功になります。
Promise.race は成功も失敗も含めて一番早く結果がでたものをその結果としますが、Promise.any は成功したもののうち一番早いものを結果とします。
Promise.any は ES2021 で追加されました。
以下が構文です。
const promise = Promise.any([promiseInstance, ... ]);
以下の場合、2番目の promise2 が一番早いですが、失敗する(reject される)ため、その次に早い3つ目の promise3 が結果になっています。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`最初に成功:${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000, true); const promise2 = delayed_action(2, 500, true);// 失敗する Promise const promise3 = delayed_action(3, 1000, true); Promise.any([promise1, promise2, promise3]) .then(value => console.log(value)) .catch( error => // 失敗したときのコールバック console.log(error.message) ); /* 出力 最初に成功:3つ目の処理(1000 ミリ秒) */
すべて失敗した場合
Promise.any に渡された Promise が全て reject された(失敗した)場合は、Promise.any の結果も失敗になり、AggregateError というエラーオブジェクトで reject されます。
以下の場合、渡された Promise はすべて失敗するので AggregateError で reject され、catch に移ります。AggregateError には message、name、errors プロパティがあります。
function delayed_action(num, ms, rejected=false) { return new Promise((resolve, reject) => { setTimeout(() => { if(rejected) { // 第3引数が true の場合は、失敗させる reject(new Error(`エラー:${num}つ目の処理(${ms} ミリ秒)`)); } resolve(`最初に成功:${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000, true); // 失敗する Promise const promise2 = delayed_action(2, 500, true); // 失敗する Promise const promise3 = delayed_action(3, 1000, true); // 失敗する Promise Promise.any([promise1, promise2, promise3]) .then(value => console.log(value)) .catch( error => { console.log(error.message); // All promises were rejected console.log(error.name); // AggregateError console.log(error.errors[0]); // Error: エラー:1つ目の処理(2000 ミリ秒) console.log(error.errors[1]); // Error: エラー:2つ目の処理(500 ミリ秒) console.log(error.errors[2]); // Error: エラー:3つ目の処理(1000 ミリ秒) } );
Async Function
ES2017 で導入された Async Function(async 関数)は非同期処理を扱う関数を定義する構文で、Promise を使った処理をより簡潔に効率よく記述することができます。
以下は fetch() を使って、指定された URL からデータを取得してコンソールに出力する関数の例です。
fetch() は Promise を返すメソッドなので、then() メソッドを使って処理を記述することができます。
const getDataThen = (url) => { fetch(url) .then((response) => { return response.json(); }) .then((data) => { console.log(data); }); } getDataThen('https://jsonplaceholder.typicode.com/users');
以下は上記を Async Function の async キーワードと await キーワードを使って書き換えた例です。
このような単純な場合は、あまり違いはありませんが、Async Function を使って記述した方が簡潔に記述できる場合があります。
const getDataAsync = async (url) => { const response = await fetch(url); const data = await response.json(); console.log(data); } getDataAsync('https://jsonplaceholder.typicode.com/users');
また、async を指定した関数は Promise オブジェクトを返すので、Promise を返すような関数を定義する場合は、簡潔に記述することができます。
以下も fetch() を使った例ですが、getApiKey() は引数に指定された値(data)を元にデータ(apikey)を取得して Promise を返す関数です。以下では then() メソッドを使って記述しています。
const getApiKey = (data) => { //Promise を返す return new Promise( (resolve) => { fetch('apikeys.php', { method: 'POST', body: data //引数で受け取った値 }) .then((response) => { return response.text(); }) .then((apikey) => { //apikey に解決される Promise を返す resolve(apikey); }); }); }
async と await を使えば以下のように簡潔に記述することができます。
const getApiKey = async (data) => { const response = await fetch('apikeys.php', { method: 'POST', body: data //引数で受け取った値 }); const apikey = await response.text(); //apikey を返すと apikey に解決される Promise が返される return apikey; }
関連ページ:Fetch API fetch() の使い方
async
Async Function(async 関数)は通常の関数とは異なり、async を関数の前に付けることで、その関数は Promise オブジェクトを返すようになります。以下が構文です。
// 関数宣言 async function 関数名(引数) { 処理 } // 関数式 const 関数名 = async function(引数) { 処理 } // アロー関数 const 関数名 = async (引数) => { 処理 } // メソッドの短縮記法 const obj = { async メソッド名(引数) { 処理 } };
例えば、以下のように定義することができます。
// 関数宣言 async function asyncFunc() { return 'hello'; } // 関数式 const asyncFunc = async function() { return 'hello'; } // アロー関数 const asyncFunc = async () => { return 'hello'; } // クラスのメソッド class MyAsync { async asyncFunc() { return 'hello'; } }
Async Function は Promise を返す
async を付けて Async Function として定義した関数は必ず Promise インスタンスを返します。
そのため、Async Function が返す Promise の結果を取得するには then メソッドや、await を使うことがことができます。
また、Async Function はその関数から return される値により、返す Promise が異なってきます。
-
- return される値が Promise でない場合
- return された値を結果に持つ Promise が fulfilled になって(成功裡に解決されて)返されます。言い換えると、return された値が戻り値の Promise の結果となります。
-
- return される値が Promise の場合
- return された Promise をそのまま返します。
-
- 例外が発生した場合
- その例外(エラー)を結果に持つ rejected になった Promise を返します。
return される値が Promise でない場合
Async Function の定義の中で return した値(返り値)が Promise オブジェクトでない場合、JavaScript は自動的にその値を結果に持つ解決された(fulfilled 状態の)Promise にラップします。
以下の Async Function は結果の値 'hello' を持つ解決された Promise インスタンスを返します。コンソールへの出力を見ると、fulfilled 状態で結果に 'hello' を持つ Promise であることが確認できます。
async function asyncFunc() { return 'hello'; } // Async Function の返す Promise を変数に代入 const promise = asyncFunc(); console.log(promise); // Promise {<fulfilled>: 'hello'}
async を付けることでその関数は常に Promise を返すため、結果を取得するには then メソッドが使えます。以下では Promise の結果を then メソッドのコールバックで出力していますが、この出力は非同期的なタイミングで実行されます。
// async キーワードを付けて Async Function を定義 async function asyncFunc() { // 返り値が promise でない場合は自動的に promise にラップされる return 'hello'; } // Async Function は Promise オブジェクトを返すので then メソッドが使える asyncFunc().then(value => { console.log(value); // hello(非同期なタイミングで実行される) }); console.log('同期的な出力'); /* 以下の順番で出力される 同期的な出力 hello */
上記の asyncFunc は以下のように記述したのと(ほぼ※)同じ意味になります。
// 返り値を Promise.resolve メソッドで記述 async function asyncFunc() { return Promise.resolve('hello'); } asyncFunc().then(value => { console.log(value); // hello });
または Promise オブジェクトのコンストラクタを使った以下でも同じことになります。
// 返り値を Promise オブジェクトのコンストラクタを使って記述 async function asyncFunc() { return new Promise(resolve => resolve('hello')); } asyncFunc().then(value => { console.log(value); // hello });
上記で(ほぼ※)と記述したのは Async Function により返された直後の Promise に微妙な違いが見られたためです(使用上は気にする必要はないことだと思いますが)。
return される値が Promise でない場合、Promise が fulfilled になって返されますが、return される値が Promise の場合は、そのまま Promise が返されるので、以下を実行すると status が fulfilled と pending と異なります(但し、PromiseState の値は全て fulfilled)。
// return される値が Promise でない場合 async function asyncFunc1() { return 'hello'; } const promise1 = asyncFunc1(); console.log(promise1); // return される値が Promise の場合 async function asyncFunc2() { return Promise.resolve('hello'); } const promise2 = asyncFunc2(); console.log(promise2); // return される値が Promise の場合 async function asyncFunc3() { return new Promise(resolve => resolve('hello')); } const promise3 = asyncFunc3(); console.log(promise3);
値を返さない場合
Async Function の定義の中で何も値を返さない場合は undefined の結果を持つ Promise を返すのと同じことになります。同期的な処理は実行されます。
async function asyncFunc() { // 何も値を返さない // return Promise.resolve(); または return Promise.resolve(undefined); と同じ console.log('hello'); // hello(同期的なタイミングで出力される) } const promise = asyncFunc(); console.log(promise); // Promise {<fulfilled>: undefined} promise.then(value => { console.log(value); // undefined });
return される値が Promise の場合
Async Function の定義の中で返り値が Promise オブジェクトの場合はそのまま返します。
// Promise オブジェクトを生成する関数 function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } async function asyncFunc(ms) { // Promise オブジェクトを返す return delayed_hello(ms); } asyncFunc(1000).then(value => { console.log(value); // 1秒後の Hello! });
上記は Promise オブジェクトを生成する関数を別途定義していますが、以下でも同じです。
async function asyncFunc(ms) { // Promise オブジェクトを返す return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } asyncFunc(1000).then(value => { console.log(value); // 1秒後の Hello! });
返り値が rejected な Promise オブジェクトの場合
返り値が rejected な Promise オブジェクトの場合(失敗が返される場合)でも rejected な Promise オブジェクトをそのまま返します。
エラーを取得するには catch メソッドまたは then メソッドの第2引数を使います。
async function asyncFunc() { // rejected な Promise オブジェクトを返す return Promise.reject(new Error('Error!')); } // catch メソッドを使用する場合 asyncFunc().catch( error => console.log(error.message)); // Error! // then メソッドの第2引数を使用する場合 asyncFunc().then( undefined, // 第1引数に undefined error => console.log(error.message) //Error! );
例外が発生した場合
Async Function の中で例外が発生した場合は、その結果(エラー)を持つ rejected な Promise オブジェクトを返します。
エラーを取得するには catch メソッドまたは then メソッドの第2引数を使います。
async function asyncFunc() { // 例外が発生 throw new Error("例外発生!"); // 例外が発生すると以降の処理は実行されません } // catch メソッドを使用する場合 asyncFunc().catch( error => console.log(error.message)); //例外発生! // then メソッドの第2引数を使用する場合 asyncFunc().then( undefined, //第1引数に undefined error => console.log(error.message) //例外発生! );
await
await は Async Function の関数の中でのみ(※)動作するキーワードで、指定した Promise が確定してその結果が返されるまで待ち、Promise の処理が完了すると解決された値を返して、Async Function 内の処理を再開(次の行の処理に移行)します。
※ ES2022 からはモジュール直下で await が使えます。
以下が await の構文です。
- expression:解決を待つ Promise または何らかの値(式)
- rv:解決された promise の値(結果)。expression が Promise ではない場合はその値自体を返す。
[rv] = await expression;
await は与えられた式(通常は Promise インスタンス)の評価結果を値として返します。
Promise が fulfilled となった場合は解決された値を返し、rejected となった場合はその場でエラーをスローします(async 関数内で例外が発生します)。
以下は1秒で解決する promise の例です。
関数の実行は await が指定されている行で一時停止し、Promise が Fulfilled または Rejected になるまで待って promise が確定したときに再開します(この例の場合は、Promise は Fulfilled になります)。
そして 変数 result には Promise インスタンスの結果の値が入ります。以下では、asyncFunc() の実行により1秒後に「1秒後の Hello!」と出力されます。
// 非同期処理の関数(指定したミリ秒後に X秒後の Hello! と表示) function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } // Async Function async function asyncFunc() { // await を指定→ delayed_hello() の非同期処理が完了するまで待つ // result には Promise インスタンスの成功(または失敗)の結果の値が入る const result = await delayed_hello(1000) ; // 以下の行は delayed_hello() の非同期処理が完了されるまで実行されない console.log(result); } asyncFunc(); //1秒後の Hello!
以下は上記を Async Function で引数を受け取るように書き換えた場合の例です。
function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } // Async Function で引数を受け取る場合 async function asyncFunc(ms) { // await を指定 const result = await delayed_hello(ms) ; console.log(result); } asyncFunc(2000); //2秒後の Hello!
上記は以下のように記述しても同じです。
async function asyncFunc(ms) { // await を指定 const result = await new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }); console.log(result); } asyncFunc(2000); //2秒後の Hello!
もし、以下のように await を指定しないと、console.log(result) は即座に実行されるため、コンソールには Promise インスタンスが出力されます。
この場合、ブラウザのコンソールタブで表示される Promise インスタンスを3秒以内にクリックして確認すると、PromiseState は pending になっていてます。
ブラウザを再読込して、すぐにはクリックせずに3秒経過してからクリックして確認すると PromiseState は fulfilled になっています。
function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } // Async Function async function asyncFunc() { // await を指定しない場合(引数には3000ミリ秒を指定) const result = delayed_hello(3000) ; // 以下の行は即座に実行される console.log(result); } asyncFunc(); // コンソールには Promise インスタンスが出力される /* 3秒以内に確認した場合 ▼ Promise {<pending>} [[Prototype]]: Promise [[PromiseState]]: "pending" [[PromiseResult]]: undefined */ /* 3秒経過後に確認した場合 ▼ Promise {<pending>} [[Prototype]]: Promise [[PromiseState]]: "fulfilled" [[PromiseResult]]: "3秒後の Hello!" */
then メソッドを使う場合との比較
以下は同じ内容を await と then メソッドを使って記述したコードです。※トップレベルの await(28行目)はファイルがモジュールの場合のみ可能です。
// 非同期処理の関数(指定したミリ秒後に X秒後の Hello! と表示) function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } // Async Function(await を使用) async function asyncFunc(ms) { const result = await delayed_hello(ms) ; // result は Promise の結果(解決された値) console.log(result); } asyncFunc(1000); //1秒後の Hello! // then メソッドを使用 function regularFunc(ms) { const promise = delayed_hello(ms) ; // promise は Promise インスタンス promise.then(value => console.log(value)); } regularFunc(1000); // 1秒後の Hello! // 関数にしない場合(then メソッドを使用) delayed_hello(1000).then(value => console.log(value)); //1秒後の Hello! // 関数にしない場合(await を使用)※注意:トップレベルで await が使えるのはモジュールの場合のみ const result = await delayed_hello(1000) ; //ファイルがモジュールの場合のみ可能 console.log(result); //1秒後の Hello!
Promise が rejected となる場合(エラーハンドリング)
await に指定した Promise が rejected となった場合は、その位置で例外が投げられ(プログラムの実行が中断し)、その async 関数は rejected な Promise を返します。
以下の場合、async 関数 asyncErrorFunc1 が返す Promise は失敗する(rejected な Promise を返す)ので、catch() や then() の第2引数で例外を補足することができます。
async function asyncErrorFunc1() { // await 式の Promise が Rejected となるのでここで例外が投げられる const result = await Promise.reject('Rejected Promise!!'); // 上記で例外が発生するので(プログラムの実行が中断するため)以降の処理は実行されない console.log(result); // これは実行されることはない } // asyncErrorFunc1 は Rejected な Promise を返すので catch() メソッドでエラーを捕捉できる asyncErrorFunc1().catch(error => { console.log(error); //Rejected Promise!! }); /* then() の第2引数で例外を補足する場合 asyncErrorFunc1().then( undefined, (error) => { console.log(error); //Rejected Promise!! } ); */
await に指定した Promise が失敗すると、await 式が例外を投げるので、その例外(エラー)は try...catch 構文でキャッチできます。そのため、以下のように、catch() メソッドや then() メソッドを使わずに、async function 内の try...catch でエラーを捕捉することができます。
この場合、asyncErrorFunc2 の中でエラーを捕捉しているので、asyncErrorFunc2 は普通に終了します。
そのため、asyncErrorFunc2 が返す Promise は成功した扱いになるので、then() メソッドの onFulfilled が実行され「完了」と出力されます。※ catch() メソッドが呼ばれることはありません。
async function asyncErrorFunc2() { try { // await 式の Promise が Rejected となるので例外が投げられる const result = await Promise.reject('Rejected Promise!!'); // 上記の await で例外が発生するので、この行には来ない } catch (e) { // 上記で発生した例外をここでキャッチできる console.log('例外発生: ' + e); // e は reject() の引数の値 } } // asyncErrorFunc2 が返す Promise は成功するので、then() メソッドが実行される asyncErrorFunc2().then(()=> { console.log('完了'); // async function は成功するので出力される }); /* 例外発生: Rejected Promise!! 完了 */ // Promise は成功するので、catch() メソッドが呼び出されることはない asyncErrorFunc2().catch((error) => { console.log('これは呼び出されません!' + error); });
以下は、上記の2つの async function が返すそれぞれの Promise をコンソールに出力しています。
asyncErrorFunc1 の返す Promise は失敗(rejected)し、asyncErrorFunc2 の返す Promise は成功(fulfilled)しているのが確認できます。
async function asyncErrorFunc1() { const result = await Promise.reject('Rejected Promise!!'); } const promise1 = asyncErrorFunc1(); console.log(promise1); /* 出力 Promise { <state>: "pending" } <state>: "rejected" // 失敗 <reason>: "Rejected Promise!!" // 失敗の結果の値 */ async function asyncErrorFunc2() { try { const result = await Promise.reject('Rejected Promise!!'); } catch (e) { console.log('例外発生: ' + e); } } const promise2 = asyncErrorFunc2(); console.log(promise2); /* 出力 Promise { <state>: "pending" } <state>: "fulfilled" // 成功 <value>: undefined // 何も値を返していないので、結果の値は undefined */
必要なら、catch 句の中で例外を投げることで、エラーを上位の呼び出し元に伝達することができます。
async function asyncErrorFunc2() { try { // await 式の Promise が Rejected となるので例外が投げられる const result = await Promise.reject('Rejected Promise!!'); } catch (error) { // 上記で発生した例外はここでキャッチされる console.log('例外発生: ' + error); // エラーを上位の呼び出し元に投げる throw error; // このエラーは catch() メソッドで捕捉される } }
Promise チェーンを await で書き換え
Promise チェーンで記述した非同期処理を Async Function と await を使って書き換える例です。
以下は Promise チェーンで記述した非同期処理の例です。
delayed_hello() は指定した秒数が経過したら「x秒後の Hello!」と表示する Promise を作成する非同期処理の関数です。
function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } // Promise チェーンでの記述 delayed_hello(300).then( // 非同期処理が成功したときのコールバック result => { // 結果(値)を出力 console.log(result); // Promise を返す return delayed_hello(500); } ).then( result => { console.log(result); return delayed_hello(700); } ).then( result => { console.log(result); return delayed_hello(1000); } ).then( result => { console.log(result); } ); /* 出力 0.3秒後の Hello! 0.5秒後の Hello! 0.7秒後の Hello! 1秒後の Hello! */
上記の Promise チェーン部分を Async Function と await を使って記述すると以下のようになります。await を使ってそれぞれの非同期処理が完了したら、その結果を出力しています。
promise.then で記述するより簡潔に記述することができます。
// Async Function と await を使って記述 async function asyncFunc() { // Promise の処理を await を付けて実行 let result = await delayed_hello(300); // 上記非同期処理が完了したら以下が実行される console.log(result); // 以下同様 result = await delayed_hello(500); console.log(result); result = await delayed_hello(700); console.log(result); result = await delayed_hello(1000); console.log(result); } // 上記 Async Function の呼び出し asyncFunc();
上記は以下のように記述することもできます。
async function asyncFunc() { // Promise の処理をすべて await を付けて実行 console.log(await delayed_hello(300)); console.log(await delayed_hello(500)); console.log(await delayed_hello(700)); console.log(await delayed_hello(1000)); } asyncFunc();
以下は乱数を生成して、生成した乱数が偶数の場合は乱数の値を成功時のコールバックに渡し、生成した乱数が奇数の場合は処理が失敗(rejected)とする Promise のインスタンスを作成して返す関数を使った Promise チェーンの例です。
この場合は、処理が失敗(rejected)となる可能性があるのでエラー処理が必要になります。
// 生成した乱数の値により成功・失敗のコールバック関数を呼び出す関数 function is_random_even() { // Promise のインスタンスを作成して返す return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10 ); setTimeout(() => { // 成功した場合(乱数が偶数の場合) if (rand % 2 === 0) { // 引数に指定した値(rand)は成功したときのコールバックに result として渡される resolve(rand); } else { // 失敗した場合(乱数が奇数の場合) // 引数に指定したエラーオブジェクトは失敗したときのコールバックに error として渡される reject(new Error(`偶数ではありません。値:${rand}`)); } }, 1000 ); }); } // Promise チェーンでの記述 is_random_even().then( // 非同期処理が成功したときのコールバック result => { console.log(`1回目の偶数です。:${result}`); // Promise を返す return is_random_even(); } ).then( // 非同期処理が成功したときのコールバック result => { console.log(`2回連続の偶数です。:${result}`); // Promise を返す return is_random_even(); } ).then( // 非同期処理が成功したときのコールバック result => { console.log(`3回連続の偶数です。:${result}`); // Promise を返す return is_random_even(); } ).catch( // 非同期処理が失敗したときのコールバック (error) => console.log(error.message) )
上記の Promise チェーンを Async Function と await 及び try...catch 構文を使って記述すると以下のようになります。失敗した場合(偶数ではない場合)はエラーがスローされます。
// Async Function と await 及び try...catch 構文を使って記述 async function asyncFunc() { try { let result = await is_random_even(); console.log(`1回目の偶数です。:${result}`); result = await is_random_even(); console.log(`2回連続の偶数です。:${result}`); result = await is_random_even(); console.log(`3回連続の偶数です。:${result}`); }catch (e) { console.log(e); } } asyncFunc();
上記は以下のように記述することもできます。
async function asyncFunc() { try { console.log(`1回目の偶数です。:${ await is_random_even() }`); console.log(`2回連続の偶数です。:${ await is_random_even() }`); console.log(`3回連続の偶数です。:${ await is_random_even() }`); }catch (e) { console.log(e); } } asyncFunc();
try...catch 構文の代わりに catch メソッドを使って記述すると以下のようになります。失敗した場合(偶数ではない場合)はエラーがスローされます。
// Async Function と await 及び catch メソッドを使って記述 async function asyncFunc() { console.log(`1回目の偶数です。:${ await is_random_even() }`); console.log(`2回連続の偶数です。:${ await is_random_even() }`); console.log(`3回連続の偶数です。:${ await is_random_even() }`); } // catch メソッドでエラーを捕捉 asyncFunc().catch(err => { console.log(err); });
Promise.all と await
Promise.all を使うと複数の非同期処理を1つの Promise インスタンスにまとめることで同時に取得することができます。
await も Promise.all メソッドと組み合わせて使うことができます。
以下は Promise.all を使って複数の Promise を使った非同期処理を並列に実行し、全ての処理が完了した時点で then メソッドのコールバック関数で forEach を使って結果を出力する例です。
// 指定したミリ秒後にメッセージを表示する非同期処理の Promise インスタンスを作成 function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } const promise1 = delayed_action(1, 2000); const promise2 = delayed_action(2, 500); const promise3 = delayed_action(3, 1000); // Promise インスタンスの配列を渡して実行し then メソッドのコールバック関数で出力 Promise.all([promise1, promise2, promise3]).then(responses => responses.forEach( response => console.log(response) )); /* 出力(同時に出力される) 1つ目の処理(2000 ミリ秒) 2つ目の処理(500 ミリ秒) 3つ目の処理(1000 ミリ秒) */
以下は Promise.all と Async Function を組み合わせて、同時に処理結果を取得する関数 delayed_action_all() の例です。
await は Async Function の中でしか使用できません。
// 指定したミリ秒後にメッセージを表示する非同期処理の Promise インスタンスを作成 function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${num}つ目の処理(${ms} ミリ秒)`); }, ms) }) } // Async Function async function delayed_action_all(params) { // delayed_action にパラメータを渡して実行し Promise インスタンスの配列を取得 const promises = params.map(function(sec, index) { return delayed_action(index + 1, sec); }); // Promise.all でラップして await させて非同期処理の結果を配列として取得 const responses = await Promise.all(promises); // 取得した結果の配列を forEach で出力 responses.forEach( response => console.log(response) ) } // delayed_action_all に渡すパラメータの配列 const params = [2000, 500, 1000]; // delayed_action_all を実行 delayed_action_all(params); /* 出力(約2秒後に同時に出力される) 1つ目の処理(2000 ミリ秒) 2つ目の処理(500 ミリ秒) 3つ目の処理(1000 ミリ秒) */
いずれかの Promise インスタンスが reject された(失敗した)場合、Promise.all は即座に rejected になり、エラーは失敗した promise から Promise.all に伝播します。
前述の Promise インスタンスを返す関数は常に成功しますが、以下は reject される(失敗する) Promise を返す可能性がある関数使う例です。
エラーは catch メソッドや then メソッドの第2引数で捕捉できます(返り値が rejected な Promise オブジェクトの場合)。
生成された Promise のいずれかが reject されると即座に(その時点で) Promise.all の reject に繋がり catch メソッドが実行されます。
function delayed_action2(num, ms) { return new Promise((resolve, reject) => { // 乱数を生成 const rand = Math.floor( Math.random() * 10); setTimeout(() => { // 生成した乱数の値により成功または失敗になる if(rand > 2) { // 成功した場合(生成した乱数が3以上の場合) resolve(`${num}つ目の処理(${ms} ミリ秒)`); }else{ // 失敗した場合(生成した乱数が2以下の場合) reject(new Error(`失敗しました: ${num}つ目の処理 rand=${rand}(${ms} ミリ秒)`)); } }, ms) }) } async function delayed_action2_all(params) { const promises = params.map(function(sec, index) { return delayed_action2(index + 1, sec); }); const responses = await Promise.all(promises); responses.forEach( response => console.log(response) ) } const params = [2000, 500, 1000]; // エラーを catch メソッドで補足する場合 delayed_action2_all(params).catch( (error) => console.log(error.message) ); // then メソッドの第2引数で補足する場合 delayed_action2_all(params).then( undefined, //第1引数に undefined error => console.log(error.message) //Error! ); /* reject された Promise の出力例(以下の場合、500 ミリ秒の時点でエラーが捕捉されます) 失敗しました: 2つ目の処理 rand=1(500 ミリ秒) */
以下は上記を async function 内でエラーを try...catch 構文で補足するように書き換えたものです。
function delayed_action2(num, ms) { return new Promise((resolve, reject) => { const rand = Math.floor( Math.random() * 10); setTimeout(() => { if(rand > 2) { resolve(`${num}つ目の処理(${ms} ミリ秒)`); }else{ reject(new Error(`失敗しました: ${num}つ目の処理 rand=${rand}(${ms} ミリ秒)`)); } }, ms) }) } async function delayed_action2_all(params) { const promises = params.map(function(sec, index) { return delayed_action2(index + 1, sec); }); try { const responses = await Promise.all(promises); responses.forEach( response => console.log(response) ); }catch (e) { console.log(e.message); } } const params = [2000, 500, 1000]; delayed_action2_all(params);
モジュールのトップレベルでの await
モジュールとして JavaScript を実行している場合は、トップレベル(関数の外の部分)において async function なしで await が利用できます(ES2022 で導入)。
※ モジュールは HTTP(s) 経由でのみ動作するため、MAMP などのローカル環境が必要です。直接 HTML ファイルを開くと same Origin Policy により、JavaScript モジュールが正しく動作しません。
そのファイルがモジュールでない場合、await は async function の中で使用する必要があります。
そのため、通常のスクリプトでは、例えば以下のように async function を使った即時実行関数で await を囲むなどが必要です。
<script> // Promise を返す関数 function delayed_hello(delay) { return new Promise((resolve) => { setTimeout(() => { resolve('Hello!'); }, delay) }) } // async function を使った即時実行関数 (async () => { // await は async function の中で使用 const result = await delayed_hello(1000); console.log(result); })(); </script>
通常のスクリプトのトップレベルで await を使用すると次のようなエラーになります。
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
ES2022 からはファイルがモジュールの場合、トップレベルで await が使用できるようになったので以下のように記述できます。
<script type="module"> // Promise を返す関数 function delayed_hello(delay) { return new Promise((resolve) => { setTimeout(() => { resolve('Hello!'); }, delay) }) } // モジュールの場合、トップレベルで await が使用できる const result = await delayed_hello(1000); console.log(result); </script>
ファイルがモジュールの場合でも、トップレベル以外の場所(async ではない普通の関数の中)で使用するとエラーになります。以下は関数の中ではないので大丈夫です。
<script type="module"> function delayed_hello(delay) { return new Promise((resolve) => { setTimeout(() => { resolve('Hello!'); }, delay) }) } const wait2000 = true; if(wait2000) { const result = await delayed_hello(2000); // OK console.log(result); }else{ const result = await delayed_hello(1000); // OK console.log(result); } </script>
非同期処理の結果をエクスポート
モジュールにおいて、トップレベルで await が使用できるようになったことで、非同期処理の結果を簡単にエクスポートできるようになりました。
export 文は必ずトップレベルに記述する必要があるため、モジュールのトップレベルで await が使用できるようになったことで、以下のように非同期処理の結果を簡単にエクスポートできるようになりました。
以下は、fetch() で {JSON} Placeholder というテスト用の JSON データを返してくれる API にアクセスして取得したデータをエクスポートする例です。
const url = "https://jsonplaceholder.typicode.com/users"; // トップレベルで await を使った(名前付き)エクスポート export const users = await fetch(url).then((response) => response.json());
<script type="module"> // 非同期処理の結果を(名前付き)インポート import { users } from './modules/users.js'; console.log(users); </script>
以下はデフォルトエクスポートの場合の例です。
const url = "https://jsonplaceholder.typicode.com/users"; const users = fetch(url).then((response) => response.json()); // トップレベルで await を使ったデフォルトエクスポート export default await users;
<script type="module"> // 非同期処理の結果を(デフォルト)インポート import users from './modules/users.js'; console.log(users); </script>
また、以下のようにエクスポートする側で async function をエクスポートして、
// async function をエクスポート const url = "https://jsonplaceholder.typicode.com/users"; export const getUsers = async () => { const response = await fetch(url); const users = await response.json(); return users; }
インポートする側のモジュールのトップレベルで await を使用することもできます。
<script type="module"> import { getUsers } from './modules/users.js'; // トップレベルで await を使用 const users = await getUsers(); console.log(users); </script>
ES2021 以前では Async Function の直下のみでしか await が利用できなかったため、例えば、以下のように Async Function を使った即時実行関数で await を囲むなどが必要でした。
<script type="module"> import { getUsers } from './modules/users.js'; (async function () { // await を async function 内で使う const users = await getUsers(); console.log(users); })(); </script>