JavaScript Promise / Async Function の使い方(非同期処理)
JavaScript ES6(ECMAScript 2015)で導入された Promise や Async Function(async/await)の基本的な使い方についての覚書です。
作成日:2020年6月17日
関連ページ:Fetch API の使い方
参考サイト
- 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
- developer.mozilla.org/GlobalFetch.fetch()
- developer.mozilla.org/Response
- まだXMLHttpRequestを使ってるの?fetchのすすめ
- ja.javascript.info/Fetch
- JavaScript Primer/Fetch API
同期処理と非同期処理
プログラムの処理は同期処理(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)。
Promise を使ってみる
前述の非同期処理における例外処理(Error-first callbacks)は単なる慣例であって、仕様ではないためいろいろな書き方ができてしまいます。また、ネストが深くなるとコードの見通しが悪くなり、変更などをする場合のコストが高くなってしまいます。
そのため、JavaScript ES6(ECMAScript 2015)で Promise という非同期処理を扱うための組み込みオブジェクトが導入されました。
Promise はチェインによってフロー制御をし、then メソッドをつなげていくことで、1つの非同期処理が終了したら次の非同期処理というように一連の非同期処理を簡単に書くことができます。
また、catch メソッドを使うことで包括的な例外(エラー)処理もできるようになっています。
Promise の大まかな仕組み
以下は Promise を使って非同期処理を行う場合の概要です。
Promise を返す関数を定義し、その関数を使って Promise のインスタンスを生成します。そして Promise のインスタンスの then() メソッドにコールバック関数を登録します。
Promise オブジェクトは new
演算子とコンストラクタ Promise(executor)
で生成します。
コンストラクタの引数 executor は2つの引数 (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) => { /* 失敗した場合の処理 */ } );
以下は前述の画像先読みの例外処理のコード(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) );
以下はアロー関数の代わりに関数リテラル(関数式)を使って記述した場合の例です。
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); } );
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() 関数のみを呼び出すこともできます。
executor はそれぞれの処理結果に応じて resolve または reject を1つだけ呼びだす必要があります。2つ以上呼び出しても2つ目以降は無視されます。
- 成功した場合に対して1つの resolve
- 失敗した場合に対して1つの reject
また、resolve() や reject() に2つ以上の引数を渡した場合、最初の引数のみが使われ、以降の引数は無視されます。
Promise オブジェクトのインスタンスが生成されると自動的に executor(関数)が呼び出されます。また、コンストラクタが promise を生成する際に、対応する resolve と reject の関数のペアも生成します。
生成された promise インスタンスは内部の(API からは直接操作できない)プロパティを持っています。
state(状態) | 説明 | result |
---|---|---|
pending | new Promise でインスタンスを生成した時の状態(初期状態) | undefined |
fulfilled | 処理が成功して完了したこと(resolve)を意味し、何らかの値(value)を持っています | value |
rejected | 処理が失敗したこと(reject)を意味し、失敗(エラー)の理由(error)を持っています | error |
pending 状態の Promise は処理結果により、fulfilled 状態、または rejected 状態のいずれかに変わり、then メソッドによって関連付けられたコールバック関数(ハンドラ)が呼ばれます。
promise オブジェクトの状態は、一度 Pending から fulfilled や rejected に遷移すると、その promise オブジェクトの状態はそれ以降変化することはありません。Event 等とは異なり、promise の状態の変化は最終的なものです。
言い換えると、executor により行われた処理は1つの結果(value)またはエラー(error)のみを持つということになります。
そして、処理の結果(値またはエラー)は then() メソッドに登録した関数で受け取ることができます。
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 と JavaScript の例外
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) { //以下の引数に指定した値(rand)は、成功したときのコールバックに引数として渡される resolve(rand); } else { //失敗した場合(乱数と一致しない) //引数に指定したエラーオブジェクトは、失敗したときのコールバック(30行目)に渡される reject(new Error(`残念でした。値:${rand}`)); } }, 1000 ); }); } //promise オブジェクトを生成 const promise = random_results(4); promise.then( // 非同期処理が成功したときのコールバック (result) => console.log(`大当たり!:${result}`), // 非同期処理が失敗したときのコールバック (error) => console.log('失敗のコールバック:' + error.message) ); /* 実行例 例外発生! */
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 として扱うことができます。以下が構文です。
var promise = Promise.all([promiseInstance, ... ]);
Promise.all メソッドは Promise インスタンスの配列を受け取り、新しい Promise インスタンスを返します。 返り値の新しい Promise インスタンスは、配列の全ての Promise インスタンスの処理が解決(resolve)した場合に fulfilled の状態になり、もし1つでも処理が失敗(reject)した場合は、返り値の Promise インスタンスも rejected の状態になります。
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, 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.all([promise1, promise2, promise3]).then(response => console.log(response)); /* 出力 ["1つ目の処理(2000 ミリ秒)", "2つ目の処理(500 ミリ秒)", "3つ目の処理(1000 ミリ秒)"] */ // forEach を使って出力 Promise.all([promise1, promise2, promise3]).then(responses => responses.forEach( response => console.log(response) )); /* 出力 1つ目の処理(2000 ミリ秒) 2つ目の処理(500 ミリ秒) 3つ目の処理(1000 ミリ秒) */
いずれかの Promise インスタンスが reject された場合、Promise.all は即座に rejected になります。
以下の場合、追加した2つ目のインスタンスが3秒後に reject され、即座に Promise.all の reject に繋がり catch メソッドが実行されます。 reject されたエラーは Promise.all 全体の結果になります。
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.all([ promise1, //rejected になる Promise インスタンスを追加 new Promise((resolve, reject) => setTimeout(() => reject(new Error('エラー発生!')), 3000) ), promise2, promise3 ]).then(responses => responses.forEach( response => console.log(response) )).catch( // 失敗したとき(rejected)のコールバック (error) => console.log(error.message) ); /* 出力 エラー発生! */
Promise.race
Promise.race は Promise.all と同様に Promise インスタンスの配列を受け取りますが、いずれか1つの Promise インスタンスの処理が完了し状態が確定(Settle ※)した時点で、その実行結果(またはエラー)を1つだけ取得します。
(※)Promise インスタンスの状態は生成時は Pending で、その後 fulfilled(成功した場合)または rejected(失敗した場合)へ変化し、それ以降状態は変化しません。 このため fulfilled または rejected の状態であることを Settled と呼びます。
以下が構文です。
var promise = Promise.race([promiseInstance, ... ]);
最初に完了した処理の結果またはエラーが Promise.race 全体の結果になります。それ以外の結果やエラーは無視されます。then メソッドのコールバック関数は一度しか呼び出されません。
以下の場合、Promise.race は最初の処理が完了する500ミリ秒後に確定(Settle)し、then メソッドはその処理の結果を受け取ります。
function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`1番:${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)); /* 出力 1番:2つ目の処理(500 ミリ秒) */
以下は、配列に追加したエラーが発生する処理が最初に確定(Settle)するので、全体の結果はエラーとなり catch メソッドのコールバックが呼ばれます。
function delayed_action(num, ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`1番:${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つ目の処理が実行される前に、2つ目の処理が確定(Settle)するのでエラーにはなりません。
//第3引数に true を指定すると失敗する関数 function delayed_action2(num, ms, error) { return new Promise((resolve, reject) => { setTimeout(() => { if(!error) { resolve(`1番:${num}つ目の処理(${ms} ミリ秒)`); }else{ reject(new Error(`Error 発生 :${num}つ目の処理でエラー`)); } }, ms) }) } const promise1 = delayed_action2(1, 2000); const promise2 = delayed_action2(2, 500); const promise3 = delayed_action2(3, 1000, true); //第3引数に true を指定 Promise.race([promise1, promise2, promise3]).then(value => console.log(value)).catch( error => // 失敗したときのコールバック console.log(error.message) ); /* 出力 1番:2つ目の処理(500 ミリ秒) */
以下の場合は、最初に完了する処理がエラーになるので、エラーが発生します。
function delayed_action2(num, ms, error) { return new Promise((resolve, reject) => { setTimeout(() => { if(!error) { resolve(`1番:${num}つ目の処理(${ms} ミリ秒)`); }else{ reject(new Error(`Error 発生 :${num}つ目の処理でエラー`)); } }, ms) }) } const promise1 = delayed_action2(1, 2000); const promise2 = delayed_action2(2, 500, true); //第3引数に true を指定 const promise3 = delayed_action2(3, 1000); Promise.race([promise1, promise2, promise3]).then(value => console.log(value)).catch( error => // 失敗したときのコールバック console.log(error.message) ); /* 出力 Error 発生 :2つ目の処理でエラー */
Async Function
ECMAScript 2017 で導入された Async Function は非同期処理を扱う(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 を関数の前に付けることで、その関数は Promise オブジェクトを返すようになります。
以下が構文です。
async function 関数名(引数) { 処理 }
アロー関数を使う場合は以下のようになります。
const 関数名 = async (引数) => { 処理 }
以下のように async キーワードを関数の定義の前に置くことで Async Function を定義できます。
async を付けることでその関数は常に promise を返すため、結果を取得するには then メソッドが使えます。
以下の Async Function は結果の値 'hello' を持つ解決された promise を返します。結果は then メソッドで確認できます。
//async キーワードを付けて Async Function を定義 async function asyncFunc() { //返り値が promise でない場合は自動的に promise にラップされる return 'hello'; } //Async Function は Promise オブジェクトを返すので then メソッドが使える asyncFunc().then(value => { console.log(value); // hello });
Async Function の定義の中で return した値(返り値)が Promise オブジェクトでない場合、JavaScript は自動的にその値を持つ解決された Promise にラップします。
上記の Async Function の定義は以下のように記述したのと同じ意味になります。
//返り値を 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 オブジェクトの場合はそのまま返します。
//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! });
値を返さない場合
Async Function の定義の中で何も値を返さない場合は undefined を返すのと同じことになります。
async function asyncFunc() { //何も値を返さない → return Promise.resolve(undefined); と同じ const hello = 'hello'; } asyncFunc().then(value => { console.log(value); // undefined });
返り値が 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 内の処理を再開(次の行の処理に移行)します。
以下が await の構文です。
- expression:解決を待つ Promise または何らかの値
- rv:解決された promise の値(結果)。expression が Promise ではない場合はその値自体を返す。
[rv] = await expression;
await は Promise インスタンスの結果を返します。Promise が fulfilled となった場合は解決された値が返され、rejected となった場合は undefined が返され、理由となった値(エラーの内容)をスローします。
以下は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!" */
前述の await と同じことを then メソッドを使って書くと以下のようなコードになります。※ await を使うとコールバック関数を使わずに記述できます。
//非同期処理の関数(指定したミリ秒後に X秒後の Hello! と表示) function delayed_hello(ms) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${ms/1000}秒後の Hello!`); }, ms) }) } //Promise インスタンスを変数に代入 const result = delayed_hello(1000); //then メソッドのコールバックを使用 result.then(value => console.log(value)); //1秒後の Hello! //関数として記述する場合は以下のようにも記述できます(冗長) function regularFunc() { const result = delayed_hello(1000) ; result.then(value => console.log(value)); } regularFunc(); //1秒後の Hello!
await を指定した Promise が rejected となった場合は undefined が返され、理由となった値(エラーの内容)をスローします。また、そのエラーの値は try...catch 構文でキャッチすることができます。
async function asyncErrorFunc() { try { // rejected となる Promise const result = await Promise.reject('Rejected Promise!!'); } catch (e) { console.log(e); // Rejected Promise!!(e:エラーの内容) } } //try...catch 構文により以下がコンソールに出力される //Rejected Promise!! //try...catch でエラーは捕捉されているので以下は実行されない asyncErrorFunc().catch(error => { console.log(error); });
await を指定した Promise が rejected となった場合、その Async Function が rejected な Promise を返すので Promise の catch メソッドでもエラーハンドリングを行えます。
async function asyncErrorFunc() { // rejected となる Promise const result = await Promise.reject('Rejected Promise!!'); console.log(result); } // catch メソッドでエラー(の値)を捕捉 asyncErrorFunc().catch(error => { console.log(error); //Rejected Promise!! と出力 console.log(error.message); // ※ undefined と出力 });
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 ミリ秒) */