JSの非同期処理を初めてES6のPromiseを使ったものに書き換えてみた

はじめに 「javascriptの非同期処理を同期ぽく綺麗に書けるようになるPromiseというのがある」 という話は少し小耳にはさんでいて、便利なんだろなーと思いつつも手が出ていませんでした。 意を決してお勉強して、自分の理解を文字に起こしつつ非同期処理をPromiseを使った形式に書き換えていってみたらけっこう分かった気がしましたし、初学者向けの記事になりえるなと思ったので公開します。 PromiseとJavascriptにおけるPromise まずPromiseとはなんぞやってことなんですが、PromiseはFutureというデザインパターンの別名らしいです。 http://ja.wikipedia.org/wiki/Future ちょっと上記から引用すると 「future, promise, delay とは、プログラミング言語における並列処理のデザインパターン。何らかの処理を別のスレッドで処理させる際、その処理結果の取得を必要になるところまで後回しにする手法。処理をパイプライン化させる。1977年に考案され、現在ではほとんどのプログラミング言語で利用可能。」 とのことですので、別に新しいものではなく、並列処理で必要なデザインパターンだと。いろんな環境でもともと実装されているけど、javascriptでは実装されていなかったので非標準ライブラリでいろいろ実装されたと。でもってやっぱり便利なので、ECMAScript6=ES6で標準で実装されることになったと。 ということで、標準になるみたいですしなら勉強しときましょうってことですね。 非同期処理をPromiseを使って書き換える ではさっそくなんとなく書き換えていきましょう。 以下からpromiseの自分の理解をつらつらと書き下しつつ, Promiseを使った処理に書き換えていくときのメモです。よって、ちょっと口調が適当です。あしからず。 ある非同期処理A(webapiの呼び出しなど)を行い、成功時には処理Bを、失敗時には処理Cを実行したいとする。 function A(callback) { callWebApi(function (error, data) { if (error) { callback(error); } else { callback(null, data); } }); } function B(data) { console.log(data) }; // これ以後の参考ソースでは略す function C(error) { console.log(error) }; // これ以後の参考ソースでは略す A(function (error, data) { if (error) { C(error); } else { B(data); } }); これをpromiseを使って書き換えることを考える。 promiseに非同期処理(もしくは処理に時間がかかるもの)を渡すとpromiseオブジェクトを作れる。promiseに渡した処理の成功時に実行したい処理をpromiseオブジェクトのthenメソッド、失敗時に実行したい処理をcatchメソッドで渡す。catchの代わりにthenの第2引数に失敗時に実行したい処理を渡すことができるのでこちらのほうが便利か。 promiseに渡す非同期処理は処理の結果次第でresolveとrejectを呼ぶ必要がある。resolveとrejectがpromiseオブジェクトに成功、失敗を伝える役目を担う。primiseオブジェクトはresolveとrejectで通知された成功、失敗の状態を保持する。 promiseに渡した非同期処理はすぐに実行される。 promiseに渡した非同期処理はすぐに実行されるため、「非同期処理がすぐに終わったらどうなるの?thenを呼ぶ前に終わったらどうなるの?怖い」と最初は思いがちだが実行結果を保持して待つため問題ない。例の場合だとpromiseにAを渡してにthenでB,Cを渡してやると、thenを呼んだ際にAが終わっていようが終わっていまいが、必ずAが終わった後にBかCが呼び出されることになる。 先のA,B,Cにpromiseを使うには、Aがerrorの有無によりコールバックを引数を変えて呼び分けていたところを、resolveとrejectを呼ぶように書き換える必要がある。そうなると「コールバックでやっていたAからB,Cへのデータの受け渡しはどうするの?」と思うけど、resolveとrejectの引数に入れればそれが素直にthen, catchの処理に渡される。 以上をふまえて書き換えると以下のようになる。 function A(resolve, reject) { callWebApi(function (error, data) { if (error) { reject(error); } else { resolve(data); } }); } p = new Promise(A); p.then(B, C); Aの中でpromiseを作るようにするとよりぽくかける。 function A() { return new Promise(function (resolve, reject) { callWebApi(function (error, data) { if (error) { reject(error); } else { resolve(data); } }); }); } A().then(B, C); これで処理の外側としては綺麗に書けるようになったけど、Aの中がPromiseのせいで余分にネストしている気がする。その対策も用意されていて、DeferredというPromiseとresolveとrejectを持つオブジェクトを利用して書き直せる。deferredの中身は{promise: <Promise>, resolve: <Function>, reject: <Function>}な感じ。 function A() { var deferred = Promise.defer(); callWebApi(function (error, data) { if (error) { deferred.reject(error); } else { deferred.resolve(data); } }); return deferred.promise; } A().then(B, C); 結果 promise使用前と使用後のソースの差は以下。B,Cについては無視してカウント。 使用前 ネスト 2回 error 6回 data 4回 callback 3回 if 2回 function 3回 () 11個 {} 7個 使用後 ネスト … Continue reading JSの非同期処理を初めてES6のPromiseを使ったものに書き換えてみた