Promise再入門(1)
モダンなJavaScriptでは、非同期処理においてpromiseとasync/awaitが主流となっている。
- Webpackはコードスプリッティングをpromiseによって簡便化している
- ブラウザにはビルトインファンクションであるfetchが存在し、resultをawaitできる
- ReactではReact.Suspenseをpromiseで実装している
ここでは従来のcallbackベースのファンクションからpromise, async/awaitまでをサンプルを通してカバーする。
Promiseとは何か?
callbackはファンクション、promiseはオブジェクト
callbackはあるイベントに対してレスポンスするかたちで実行されるファンクション。どんなファンクションでもcallbackになることができ、どんなcallbackでもファンクションになることができる。
promiseはオブジェクトであり、あるイベントがまだ起こっていない状態(pending)を表現する。あるイベントが起きたかどうかに関する情報、起きた場合の出力の情報を保持している。
callbackは引数として渡され、promiseは返却される
callbackは独立して定義され、あるファンクションに引数として渡され、必要なタイミングまで保持され、関連するイベントが起きたタイミングで実行される。
promiseは非同期の処理を開始させるファンクションによって作られて返却される。関連するイベントが起きたとき、その処理は実行結果をpromiseに格納し、promise側でsuccessまたはfailureハンドラを実行できる。
callbackはsuccessとfailureを扱い、promiseは何も扱わない
callbackはある処理が成功したかどうかの情報とともに使用され、成功時と失敗時のハンドラを用意しておかないといけない。
promiseはデフォルトでは何も扱わないが、successまたはfailureハンドラは後々にアタッチされる。
callbackは複数のイベント扱うことができるが、promiseは最大1つ
callbackはそれらを受け取るファンクションによって複数回実行することができる
promiseは一つのイベントだけを表現することができ、それは成功時または失敗時のものである
CallbackをPromiseで書き換える一例
ログを一定時間ごとに出力するサンプルを下記に示す。
Callback
const log = (message = '') => { console.log(message) } log('When the raining is ') setTimeout(() => { log('blowing ') setTimeout(() => { log('in your face,') }, 1000) }, 2000)
Promise
const log = (message = '') => { console.log(message) } // delay promise const delay = ms => { return new Promise(resolve => { setTimeout(resolve, ms) }) } async function logMessage() { log('When the raining is ') await delay(2000) log('blowing ') await delay(1000) log('in your face,') } logMessage()
Thenメソッド
Promiseはまだ起きていないイベントの処理結果を表すものである。 処理結果の取得可能時にcallbackファンクションを自分で登録して、promiseのthenメソッドで渡す必要がある。
// Call the first argument once the promise has successfully resolved promise.then(value => { // ... }); // Call the second argument if the promise is rejected promise.then(null, error => { // ... }); // You can handle both cases by passing in two functions promise.then( value => { /* ... */ }, error => { /* ... */ } );
Rejectionハンドラ
滅多にないと思うが、もしsuccessハンドラでエラーが起きた場合、当然ながらrejectionハンドラには進まない。
ただし、catchを書いている場合はそちらの処理が実行される。
ここは以外な盲点なので、下記にサンプルを含めて載せておく。
const samplePromise = new Promise((resolve, reject) => resolve('Success!')) samplePromise.then( success => { console.log('success handler', success) // これが出力される throw new Error('here is an error!') // これが出力される }, error => { console.log('rejection handler', error) } )
const samplePromise = new Promise((resolve, reject) => resolve('Success!')) samplePromise.then( success => { console.log('success handler', success) // これが出力される throw new Error('here is an error!') }, error => { console.log('rejection handler', error) } ) .catch(error => { console.log('catch handler', error) // これが出力される })
上記の例ではcatch内でエラーをログするようにしているが、そのようにエラーハンドリングしっかりとしてあげることでデバッグができることを念頭においておく。
const samplePromise = new Promise((resolve, reject) => reject('Failed!')) samplePromise.then( success => { console.log('success handler', success) throw new Error('here is an error!') }, error => { console.log('rejection handler', error) // これが出力される } ) .catch(error => { console.log('catch handler', error) })