こんにちは。ウェブ共同開発部エンジニアの小原です。
「Fenrir Advent Calendar 2015」の 16 日目です。テーマは「次世代 JavaScript でコールバック地獄からオサラバ!」です。
ついに今年、ES6 が来ました! 中でも Promise にグッと来たため紹介します。
JavaScript では Promise 自体はライブラリなどで古くからあるので、目新しさは薄いかもしれませんが、素晴らしいことに ES6 に含まれるようになりました。 なので ES6 と絡めて紹介したいと思います。
ES6 とは
2015 年 6 月、ECMAScript 2015 (6th Edition) が公開され、フロントエンドエンジニアの間で注目が高まってきています。 ECMAScript というのは簡単に言うと各ブラウザが JavaScript エンジンを実装する際に参考にする言語仕様のことで、現在ほとんどのブラウザがこの ECMAScript という仕様に従って JavaScript を実装しています。
この ECMAScript が今年 2015 年に更新されました。これが ES2015 や ES6 などと呼ばれています。タイプ量の関係で以降では ES6 と書くことにします。
JavaScript の弱点を補うため、以前から CoffeeScript や TypeScript といった AltJS がありました。 これら AltJS は JavaScript への変換を必要としますが、ES6 は JavaScript の仕様なので書いたコードをそのまま実行できます。 現在のところ各ブラウザが ES6 を実装中のため、まだ完璧には利用できません。 しかし、近い将来により洗練された JavaScript の書き方を試せるようになるはずです。
ほんの少しだけ ES6 のコードを載せておきます。
// ES5 var Hoge = function() { this.fuga = 'hello'; } Hoge.prototype.piyo = function() { console.log(this.fuga); } // ES6 class Hoge { constructor() { this.fuga = 'hello'; } piyo() { console.log(this.fuga); } }
クラス定義だけでもかなり読みやすい書き方で書けるようになってますね。
ES6 と Promise
ES6 は現在主流の ES5 に比べると、かなり多くの変更が加えられています。 詳細に見ていくとワクワクする点が結構あるのですが、特にグッときたのは Promise が取り込まれたことです。
これで Promise を使ってコールバック地獄からオサラバです!
コールバック地獄
JavaScript はネットワーク通信を扱うシーンが多いこともあって、頻繁に非同期処理を書く言語だと言えます。 例えばこんな感じです(ES6 の文法は使わず書いています)。
asyncProcess(option, function(processResult) { finishProcess(processResult); });
asyncProcess
関数の引数として関数を渡しています。これをコールバック関数と呼びます。 仮に同じ方法でpostProcess
という非同期処理を追加した場合、こうなります。
asyncProcess(option, function(processResult) { postProcess(processResult, function() { finishProcess(); }); });
このように非同期処理の連鎖を書こうと思うと、どんどんコールバック関数をネストしなければなりません。 関数を変数に入れればネストを深くしないで書くこともできますが、今度は実行順序がわかりにくくなるという問題があります。 これがコールバック地獄と言われるものです。
asyncProcess(); postProcess(); finishProcess();
このように簡単に書きたいですよね。
JavaScript を取り組んでコールバック地獄に苦い思いをした人も多いかと思います。
Future パターン
コールバック地獄は根幹に非同期処理の難しさという問題があります。しかし、これはどうしようもない問題というわけでもなく、対策として Future パターンというデザインパターンが知られているようです。
Future パターンというのは非同期処理で使われるパターンで、未決定の値を包み、以降の処理を未決定の値が決定されるまで先送りする、というものです。 Promise というのはこの Future パターンそのもの、ただの別名です。
この Future = Promise パターンを使って、コールバック地獄をなんとかしようとすると、このようなコードになります。
(new Promise(function(resolve) { asyncProcess(option, resolve); })) .then(function(processResult) { return new Promise(function(resolve) { postProcess(processResult, resolve); }); }) .then(function() { finishProcess(); });
少し複雑ですが、外側のコードだけに注目すると、
(new Promise( ... )) // asyncProcess の処理 .then( ... ) // postProcess の処理 .then( ... ); // finishProcess の処理
という流れになっています。 逐次的な処理をそのままコードにできていますね。もし 100 個の非同期処理を書こうと思ったとしてもthen
のメソッドチェーンで書くことができます。 このように書くことで間に処理を追加したり削除したりすることが容易になります。
Promise/A+
今まで Promise パターンを利用するにはライブラリを使う必要がありました(ES5 ではnew Promise()
というコードはライブラリを利用しない限りエラーになる)。 Promise パターンを実装したライブラリは数多くあるのですが、実はそれらを相互に組み合わせて使えるように、Promise/A+ というコミュニティーベースの仕様が決まっています。多くのライブラリがこの仕様を満たした実装をしているため、知らずに使っていたという人もいるかもしれません。
そして、ES6 に導入された Promise も Promise/A+ に従った仕様です。 つまり、ライブラリを導入せずに今までの知識で Promise が使えるようになります。 コールバック地獄の一つの解決策が JavaScript の言語仕様としてサポートされたわけです!
Promise はいつから使えるのか?
先にも書いたとおり、仕様が公開されたのが今年の 6 月のことなので、実装はどうなんだと思われかもしれません。 こちらの実装状況の表を参考にさせていただくと、「最新のブラウザ」では問題なく使えそうです。 しかし Internet Explorer 11, Android 4.4, iOS 7 以前では使えないので、Web ページで利用するのはまだまだ難しそうです。
ただ、現状でも Babel などのトランスパイラーや Polyfill と呼ばれるツールを使えば十分に利用できます。これらを使って ES6 の Promise を使ってみてもよいかもしれません。
まとめ
気を抜くとコールバックの多用によって混沌とする JavaScript に 2015 年、満を持して Promise がやってきました。 言語仕様として Promise がサポートされたことによって、これからは(ライブラリを使うにしても、ES6 で書くにしても)Promise を大手を振って使うことができます。 どんなパターンでコールバック地獄を解決するか頭を悩ませていた人には非常にグッと来たのではないでしょうか。
より良くなってきた JavaScript を使い、これからも保守しやすいコードを目指して開発していきたいです。
フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!