JavaScriptを扱うにあたって避けては通れない道、Ajaxとかタイマーとかの非同期処理…
ES7のawait/asyncを学ぶ方が良いのでしょうが、jQuery.deferred
辺りで止まってしまっていると、いきなりawait/async
は辛いので、Promise
をざっくり理解してawait/async
に置き換える方法で学習するのが良いかと思い今更のPromise入門です。
Promiseの基本
new Promise(function(resolve, reject) {...})
resolve
... 処理が成功したら呼び出す関数reject
... 処理が失敗したら呼び出す関数
function hi(name) { return new Promise((resolve, reject) => { setTimeout(() => { if(name) { resolve(`Hi, ${name}!`) } else { reject('ERROR name is empty!') } }, 100) }) }
.then()
Promise.prototype.then(successCallback, failureCallback)
返されるPromiseオブジェクトに.then
をメソッドチェーンさせることで、Promiseからresolve
/ reject
が呼び出されるまで待機して値を受け取れる
successCallback
...Promise.resolve
が実行された時に呼び出される関数 (成功時に呼ばれる関数)failureCallback
...Promise.reject
が実行された時に呼び出される関数 (失敗時に呼ばれる関数)
function onSuccess(res) { console.log('onSuccess', res) } function onReject(error) { console.log('onReject', error) } hi('星宮いちご').then(onSuccess, onReject) // => onSuccess Hi, 星宮いちご! hi().then(onSuccess, onReject) // => onReject ERROR name is empty!
※ 非同期処理を同期的に実行したいときネストが深くなってしまう
hi('星宮いちご').then((res1) => { console.log('onSuccess 1', res1) hi('霧矢あおい').then((res2) => { console.log('onSuccess 2', res2) hi('紫吹蘭').then((res3)=> { console.log('onSuccess 3', res3) }, onReject) }, onReject) }, onReject) // => onSuccess 1 Hi, 星宮いちご! // => onSuccess 2 Hi, 霧矢あおい! // => onSuccess 3 Hi, 紫吹蘭!
then
のコールバック内でPromiseを返す関数をreturn
すれば、Promise.then
をメソッドチェーンにすることができる
hi('星宮いちご').then((res1) => { console.log('onSuccess 1', res1) return hi('霧矢あおい') }) .then((res2) => { console.log('onSuccess 2', res2) return hi('紫吹蘭') }) .then((res3) => { console.log('onSuccess 3', res3) }) // => onSuccess 1 Hi, 星宮いちご! // => onSuccess 2 Hi, 霧矢あおい! // => onSuccess 3 Hi, 紫吹蘭!
※ ただしメソッドチェーンしたthen
内のコールバックはスコープが異なるので、前のthen
のコールバックで受け取った値を直接参照することはできない
hi('星宮いちご').then((res1) => { return hi('霧矢あおい') }) .then((res2) => { coneolse.log(res1) // => ReferenceError: res1 is not defined })
※ エラーになった場合の処理が記述されてない場合、Promise.reject()
が発生するとワーニングになってしまう。
hi() .then((res1) => { console.log('res1') return hi() }) .then((res2) => { console.log('Promise Complete') }) // => UnhandledPromiseRejectionWarning: Unhandled promise rejection // => DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
.catch()
Promise.prptpyupe.catch(onRejected)
Promise
が失敗した時に実行される節
エラーになった箇所以降のthen
は実行されない
hi('神前美月').then((res1) => { console.log('onSuccess 1', res1) return hi() // Promise.rejectになる }) .then((res2) => { console.log('onSuccess 2', res2) return hi('藤堂ユリカ') }) .then((res3) => { console.log('onSuccess 3', res3) }) .catch((error) => { // 共通のエラー処理 console.log('Catch Error:', error) }) // => onSuccess 1 Hi, 神前美月! // => Catch Error: ERROR name is empty!
非同期処理を同期的に実行する時、特別なことがなければエラーはcatch
で取るのが良さそう。
Promise.all
Promise.all()
... 引数で渡された全ての処理が完了したら結果の配列が返される。実行する処理に1つでもエラーがあるとエラーになる。
Promise.all([ hi('神前美月'), hi('藤堂ユリカ'), hi('一ノ瀬かえで') ]) .then((res) => { // すべての非同期処理が完了したら、結果がArrayで返される console.log(res) }) .catch((error) => { console.log(error) }) // => [ 'Hi, 神前美月!', 'Hi, 藤堂ユリカ!', 'Hi, 一ノ瀬かえで!' ]
1つでもエラーがあると、最初にエラーになったものが返る
const task1 = new Promise((resolve, reject) => setTimeout(resolve, 50, 'TASK1')) const task2 = new Promise((resolve, reject) => setTimeout(reject, 200, 'TASK2')) const task3 = new Promise((resolve, reject) => setTimeout(reject, 100, 'TASK3')) Promise.all([ task1, task2, task3 ]) .then((res) => console.log(res)) .catch((error) => console.log(error)) // => TASK3
Promise.race
Promise.race()
... 引数で渡された処理のいずれか1つが完了したら、完了した処理の値が返される。完了した最初の処理がエラーならエラー処理に流れる。※ 成功するものがあるか待つわけではない。
const task1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '音城セイラ')) const task2 = new Promise((resolve, reject) => setTimeout(resolve, 50, '冴草きい')) const task3 = new Promise((resolve, reject) => setTimeout(resolve, 10, '風沢そら')) Promise.race([ task1, task2, task3, ]) .then((res) => { // 最初に完了した処理の返り値がそのまま渡される console.log(res) }) .catch((error) => { console.log(error) }) // => 風沢そら
最初に返されるものがエラー(reject
)ならcatch
に、並列処理中にエラーになるものがあっても、最初に返るのが正常な値(resolve
)ならthen
に処理が流れる
const task1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '大空あかり')) const task2 = new Promise((resolve, reject) => setTimeout(resolve, 50, '氷上すみれ')) const task3 = new Promise((resolve, reject) => setTimeout(resolve, 10, '新条ひなき')) const taskError = new Promise((resolve, reject) => setTimeout(reject, 30, 'ジョニー別府')) Promise.race([ task1, task2, taskError ]) .then((res) => console.log(res)) .catch((error) => console.log(`Error: ${error}`)) // => Error: ジョニー別府 Promise.race([ task1, task2, taskError task3, ]) .then((res) => console.log(res)) .catch((error) => console.log(`Error: ${error}`)) // => 新条ひなき
Promise.resolve() と Promise.reject()
非同期処理がない場合など、わざわざPromise
オブジェクトを返す必要がない関数の場合、関数内でPromise.resolve()
、Promise.reject()
を返すとと呼び出し元のPromise
に返すことができます。
※ return
しないと、関数が終了したundefined
が返される
function hi(name) { if( name.indexOf('ジョニー') === -1 ) { return Promise.resolve(`Hi, ${name}!`) } else { return Promise.reject(`ERROR: You are ${name}!`) } } Promise.all([ hi('紅林珠璃'), hi('天羽まどか'), hi('黒沢凛') ]) .then((res) => console.log(res)) .catch((error) => console.log(error)) //=> [ 'Hi, 紅林珠璃!', 'Hi, 天羽まどか!', 'Hi, 黒沢凛!' ] Promise.all([ hi('ヤミ'), hi('ミヤ'), hi('ジョニー別府'), ]) .then((res) => console.log(res)) .catch((error) => console.log(error)) //=> ERROR: You are ジョニー別府!
Promiseの基本的な使い方をまとめてみました。
実際の使われた方とかアンチパターンとかは、まだ解ってないですが... (その辺りはawait/async
に移行しちゃってから考えたほうが良い気がします)
[参考]