JavaScriptのPromiseについて調べた
JavaScript再入門中。
async/await
を覚えるにあたってPromise
をおさらいしないといけなさそうだったのでメモ。
Promiseとは
Promiseは非同期処理の最終的な完了もしくは失敗を表すオブジェクトです。 Promiseを使う - MDN
// 構文 new Promise( function(resolve, reject) { ... } );
Promiseの状態
生成されたPromise
オブジェクトは、以下の状態を持つ。
- pending: 初期状態。成功も失敗もしていない。
- fulfilled: 処理が成功して完了した。
- rejected: 処理が失敗した。
Promise
コンストラクタに与えられた関数(executor
関数)には、2つの引数resolve
関数とreject
関数が渡される。
executor
関数は内部でなんらかの非同期処理を実行し、
成功した場合にはresolve
関数を呼び出すことでfulfilled
に、
失敗した場合にはreject
関数を呼び出すことでrejected
にPromise
オブジェクトの状態を変更できる。
また、executor
関数で例外がスローされた場合、そのPromise
オブジェクトの状態はrejected
となる。
var promise1 = new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", 'http://example.com'); xhr.onload = () => resolve(xhr.status); // resolveは成功を表す xhr.onerror = () => reject(xhr.status); // rejectは失敗を表す xhr.send(); }); var promise2 = new Promise((resolve, reject) => { throw new Error('error!'); // 例外がスローされた場合はrejected });
then / catch / finally
生成したPromise
オブジェクトは、インスタンスメソッドthen
を使って成功時/失敗時の処理を登録することができる。
Promise
オブジェクトが成功(fulfilled
)時には第一引数の関数が、失敗(rejected
)時には第二引数の関数が実行される。
また、then
の他に、失敗時の処理のみを登録できるcatch
メソッド、Promise
オブジェクトの成功/失敗に関わらず必ず実行されるfinally
メソッドがある。
promise.then( function(value){ /* 成功時の処理 引数valueには、reslve()に渡された値が入っている */ }, function(reason){ /* 失敗時時の処理 引数reasonには、reject()またはError()に渡された値が入っている */ } ); promise.catch( function(reason){ /* 失敗時時の処理 */} ); //catchを使った書き方は、以下の書き方と等価 promise.then( undefind, function(reason){ /* 失敗時時の処理 */} ); promise.finally(function(){ /* 成功/失敗共通の処理 finallyメソッドは引数をとらない。 */ });
Promiseチェーン
実行されたthen
/catch
/finally
メソッドは、新しくPromise
オブジェクトを返す。
そのため、メソッドチェーンのように書くことができる。
var promise = new Promise(function(resolve, reject) { setTimeout(function() { resolve(0); }, 300); }); promise .then( value =>{ console.log(value); // コールバック関数内でreturnすることで、値を後続の処理に引き渡せる。 return ++value; }) .then( value =>{ console.log(value); return ++value; }) .then( value =>{ console.log(value); return ++value; }) ; // 出力は以下のようになる // 0 // 1 // 2
なお、メソッドチェーンの途中で例外が発生した場合、その処理は中断され、後続の最も近いcatch
メソッドもしくはthen
のエラー処理関数が実行される。
var promise = new Promise(function(resolve, reject) { setTimeout(function() { resolve(0); }, 300); }); // catchによる例外処理 promise .then( value =>{ console.log(value); return ++value; }) .then( value =>{ // 例外をスロー throw new Error('Error!'); }) .then( value =>{ // 前の処理で例外がスローされているため、この処理は実行されない console.log(value); return ++value; }) .catch(error => { console.log(error); }) ; // 出力は以下のようになる // 0 // Error: Error!
var promise = new Promise(function(resolve, reject) { setTimeout(function() { resolve(0); }, 300); }); // thenのエラー処理関数による例外処理 promise .then( value =>{ console.log(value); return ++value; }) .then( value =>{ // 例外をスロー throw new Error('Error!'); }, error =>{ // 同じthenメソッドに登録されているエラー処理関数は実行されない。 console.log(`then1: ${error}`); }) .then( value =>{ // 前の処理で例外がスローされているため、この処理は実行されない console.log(value); return ++value; }, error =>{ console.log(`then2: ${error}`); }) .catch(error => { // 前処理のthenで例外が処理されているため、この処理は実行されない。 console.log(`catch: ${error}`); }) ; // 出力は以下のようになる // 0 // then: Error: Error!
Promise.resolve と Promise.reject
Promise
オブジェクトの生成にはnew Promise()
の他にもシンタックスシュガーとしてPromise.resolve
およびPromise.reject
という静的メソッドが用意されている。
Promise.resolve
は成功状態の、Promise.reject
は失敗状態のPromise
オブジェクトを返却する。
Promise.resolve('success'); // 上記は以下と等価 new Promise(function(resolve){ resolve('success'); }); Promise.reject(new Error('error!')); // 上記は以下と等価 new Promise(function(resolve,reject){ reject(new Error('error!')); });
Promise.all と Promise.race
複数のPromise
オブジェクトをまとめて処理する方法として、Promise.all
と Promise.race
がある。
Promise.all
は引数にPromise
オブジェクトの配列を取り、すべてのオブジェクトが成功状態のとき後続のthen
の成功時処理を実行する。ひとつでも失敗するとthen
は実行されず、catch
またはthen
の失敗処理が実行される。
Promise.all([ Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), ]) .then(values =>{ console.log(values[0]); console.log(values[1]); console.log(values[2]); }); // 出力は以下 // 1 // 2 // 3 Promise.all([ Promise.resolve(1), Promise.reject(new Error('error')), Promise.resolve(3), ]) .then(values =>{ console.log(values[0]); console.log(values[1]); console.log(values[2]); }) .catch(error =>{ console.log(error); }); // 出力は以下 // Error: error
Promise.race
もまた引数にPromise
オブジェクトの配列を取り、どれか一つでも成功または失敗の状態になったら後続の処理を実行する。
var promise = Promise.race([ new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open("GET", 'http://example.com'); xhr.onload = () => resolve(xhr.status); xhr.onerror = () => reject(xhr.status); xhr.send(); }), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); promise .then(value => console.log(value)) .catch(error => console.log(error));
なお、後続の処理に引き渡されるのは最初に解決したPromise
オブジェクトの結果のみだが、他の処理が中断されるわけではないので注意が必要。
var promise = Promise.race([ new Promise(function (resolve, reject) { console.log('promise 1'); setTimeout(() => reject(new Error('request timeout')), 5000) }), new Promise(function (resolve, reject) { console.log('promise 2'); setTimeout(() => reject(new Error('request timeout')), 1000) }) ]); promise .then(value => console.log(value)) .catch(error => console.log(error)); // 出力は以下 // promise 1 // promise 2 // Error: request timeout
記事中のソースは以下にまとめてあります。