Promiseはコールバックに対する聖杯ではない

ES6ならびにDOM4にPromiseが投入されることとなり、すっかりJavaScriptでよく陥るコールバック地獄に対する至高の解決策のように扱われているPromiseだが、万能の解・聖杯ではない。

たぶん誰かが既に似たようなこと書いてると思うけど、とりあえず自分の思考の整理に書く。

Promiseといえば以下のようにコールバックによるコードの無限ネストを解決するものとして扱われることが多い。

var p = new Promise();
p.then(function () {
...
}).then(function () {
...
})

だが、これはコールバックをPromiseというラッパーを用いてネストしない形に変換しただけに他ならず、Promiseの利点ではない。適当なラッパー関数を作って解決しているのと大差はない。

本質的にはPromiseとは将来的などこかの時点になれば値で満たされるデータの器であり、何かしらの操作に対する最終的な結果を受け取るために用いる抽象化コンテナだ。故にPromiseは、関数間でPromise形式のデータを受け渡しすることによってそのコンテナとしてのメリットを発揮するのであり、一つの関数内部でコールバックのネストを予防するために数珠のようにつなげる場合であれば、Promiseを使う必要性は特になく、がんばってコールバックをネストして書くのと大差はない。見た目が異なるだけだ。ついでに言うと、Promiseを一つの関数内で延々数珠つなぎをするのは関数一つに対して責務が巨大になり始めているのと同義であり、構造化の破綻の兆しが見えているので、APIとしては分解された方がよい。

関数型言語方面によく見られるOption型, Maybe型のように取り扱うのが本義である(というかまさにそれ)(尚、Option型の説明は"「値が無い」ということをどう表現するか、あるいはOption型について - Line 1: Error: Invalid Blog('by Esehara' )"が、非常にわかりやすいのでおすすめ)。

また、Promiseは、将来的な時間軸のどこかで決定された値が入ることにより状態が確定されるAPIである。よって、進捗率に応じて定期的・継続的にコールバック処理を行う必要がある場合にPromiseを使用するのは概念モデルとして破綻を来すのは間違いない(事実、ProgressPromiseといういかがわしいデータ型がDOMに導入されかかったものの結局取りやめになったのは、それが継続処理をPromiseで解決しようという無理難題をふっかけてしまった結果と聞いている)。継続的な処理を行うのであれば、むしろStreamなどの概念に活路を求めるべきであろう。

などと偉そうに色々書いたところで、w3ctag/promises-guide · GitHubで同じようなことが延々と述べられていて、最初からコレを貼付ければ、こんなに無駄に長々とまどろっこしく書く必要なかったのになあと思うのでした