ライセンスの下に提供されています。はじめにJavaScriptの多くの実装は、シングルスレッドで動作するようになっています。その言語の意味ゆえ、並行処理の命令にコールバックを使いがちです。JavaScriptで継続渡しスタイル(CPS)を使うのは特に間違いではありませんが、読みづらいコードになりやすく、必要以上に手順が増えるのも事実です。これに関して多くの解決法が提案されており、Promiseを利用してこれらの並行処理を同期するという方法もそのひとつです。この投稿では、Promiseとは何か、どのように動作するのか、使うべき状況、使うべきでない状況とあわせて見ていきます。注この記事は、少なくとも高階関数、クロージャ、コールバック(継続渡しスタイル)を理解している読者を対象にしています。以上の知識なしで読んでも得るところはあるかもしれませんが、まずそれらの概念の基礎を理解しているほうがよいでしょう。Promiseの概念を理解する最初に、重要な問いに答えましょう。「ところで、Promiseって何?」この問いの答えを得るため、現実の世界で、非常に一般的なシナリオを考えてみます。Hackerとその女友達は、とても人気のあるレストランで食事をすることにしました。予想されたとおり、レストランに到着するとテーブルは全ていっぱいでした。他の店であれば、彼女たちはあきらめるか、別の場所で食事をするか、テーブルが開くまで長い列に並ぶかを選ぶことになったでしょう。Alissaは行列が嫌いでした。このレストランには彼女にとって完璧な解決法があったのです。「心配しないで、お客様。この装置があなたのために全て手配します」と、小さな箱を持ったレストランの女性が言いました。「これは、レストラン内のあなたの未来のテーブルを表す魔法の装置です...」。女性が答え、Baeを手招きしました。「本当は魔法ではないのですが、これはテーブルの準備が整い、あなた方が食事ができるようになったら教えてくれるのです」。Promiseとは何か?あなたの未来のテーブルのために代わりに並んでくれる”魔法の”装置のように、Promiseは将来使えるようになる何かを表すために存在します。プログラミング言語では、それは値です。リンゴをまるごと入れると、スライスが出てくる。同期処理の世界では、関数の計算は非常にシンプルに理解できます。何かを関数に入れると、関数は何かを返します。この入力すると出力されるモデルはとても分かりやすく、多くのプログラマにとって最も身近なものです。JavaScriptの全ての構文構造と組み込み関数は、あなたの書く関数がこのモデルに沿うことを仮定しています。このモデルには1つ大きな問題があります。何かを関数に入れることで美味しい何かを得るためには、そこに座って関数が仕事を終えるのを待つ必要があるのです。理想としては、座って待つよりも、自分の時間でできるだけたくさんのことをしたいと誰もが思うでしょう。この問題を解決するべく、Promiseは、欲しい値を待つ代わりに、その値の代替となるものを即座に得る方法を提案します。すれば私たちは自分たちの生活を続け、後のどこかの時点で、欲しい値を取りに戻って来るということができます。Promiseは最終的に得られる値の代理品リンゴをまるごと入れると、美味しいリンゴのスライスを後で入手するためのチケットが出てくる。幕間:実行順序では、Promiseとは何かを理解したところで、Promiseによって並行処理プログラムをより簡単に書く方法を見ていきます。その前に、1歩戻って、より根本的な問題、プログラムの実行順序について考えましょう。JavaScriptのプログラマとして、プログラムが極めて独特な順序で実行されることに気づいた人もいるかもしれません。それはプログラムのソースコード内に書かれた命令の順序なのです。20;このプログラムを実行すると、JavaScript仮想マシンはcircleAreaの計算を行い、それが済むと、squareAreaの計算を行います。言い換えれば、プログラムはマシンに「これをやりなさい。それからあれをやりなさい。それから...」と説明しているのです。ここでクイズの時間です。マシンはsquareAreaより先にcircleAreaを実行しなければならないのでしょうか。順序を逆にしたり、2つを同時に実行したりすると何か不都合があるのでしょうか。結局、全てを順番に実行すると非常にハイコストなのです。circleAreaの実行に長い時間がかかるなら、それが終わるまでsquareAreaは全く実行されません。実際、この例でも、どのような順序にするかは問題ではありません。結果は常に同じなのです。プログラムに表現されている順序は非常に恣意的です。順序は非常にハイコストなのです。私たちのコンピュータには、より多くのことをより速く行わせたいのです。そのためにまず、実行順序を完全に省きましょう。プログラムの中で、全ての式がきっかり同時に実行されるようにするのです。このアイデアは先の例ではうまく動作します。異なることを試すと破綻します。20;print(circleArea);順序を全く持たない場合、他の式から得られる値を利用するにはどうすればいいのでしょう。値が必要な時間までに計算されるという保証はないからです。別の方法を試しましょう。プログラム内の唯一の順序は式の構成要素の間にある従属性によって定義されます。それは原則として、式は、他のことが既に実行されていたとしても、その全ての要素の準備ができ次第、実行されるということです。単純な例の従属性グラフ実行の際にプログラムがどのような順序で実行されるべきかを宣言する代わりに、各計算が互いにどのように関わり合っているかだけを定義しました。そのデータをもとに、コンピュータは上記の従属性グラフを作成し、最も効率的なプログラム実行方法を自ら把握します。上記グラフはプログラミング言語Haskellにおいてプログラムが評価されるさまを如実に記述しています。同時に、Excelのようなより有名なシステムが式を評価する方法にも非常に似ているのです。前の章で説明した、順序を式の間の従属性で明快に定義する実行モデルは力強く効率的ですが、JavaScriptに適用するにはどうしたらよいのでしょうか。このモデルを直接JavaScriptに適用することはできません。言語の意味が本質的に同期式であり、かつ先頭から順に動くものだからです。別のメカニズムを作って式の間の従属性を記述し、これらの従属性を解決して、独自のルールに沿ってプログラムを実行させることはできます。1つの方法は、この従属性の概念をPromiseの中核に導入することです。そこでPromiseの意味を改めて考えると、2つの大きな要素で成り立つことになります。1つは「値の代理品を作り、その代理品の中に値を入れることができる」ということ。それから、「1つの式と1つの値の間に従属性を作り、その式の結果を出すための新しいPromiseを作る」ということです。値と式の間の従属性を作成ここにおけるPromiseは、まだ計算していない値を表します。この代理品は不明瞭で、値は見えず、値と直接対話することもできません。JavaScriptのPromiseでは代理品から値を抜き出すこともできません。一度JavaScriptのPromiseに何かを入れると、Promiseから取り出すことはできないのです1。このことはさほど有用ではありません。何とかしてこれらの値を使う必要があるからです。代理品から値を抜き出せないとすると、それらを使うために別の方法を理解しなければなりません。結局、”抜き出しの問題”を解決する最も単純な方法は、プログラムにどう動作させたいのかを記述し、明示的に従属性を与え、実行のためにその従属性グラフを解決することです。そのためには、実際の値を式の中に差し込み、この式の実行を確かに必要な時まで延期させる術が求められます。JavaScriptには、第一級関数がまさにこの目的のために存在しています。1の形の式がある場合、この式で、一度準備ができればaが差し込まれ得る値になる、といった抽象化を行うことが可能です。3下のようなラムダ抽象化になります2。3第一級関数は非常に強力な概念です(それらが匿名のラムダ抽象化であってもなくても)。第一級関数を持つJavaScriptは、後で値を差し込めるようPromiseの値を使った式を第一級関数に変換することによって、これらの従属性をとても自然に書くことができます。Promiseで式を順序付けするPromiseの概念の性質を見直したので、次はマシンの内部でどのように動くかを理解しましょう。これからPromiseを生成するのに使う演算を記述し、値を置き、式と値の間の従属性を書きます。サンプルのため、偶然、既存のPromise実行には使われない、次のような非常に説明的な演算を使います。createPromise()は値の代理品を生成します。値は後の時点で提示されます。value)はPromiseの中に値を置き、値に依存する式が計算されるようにします。expression)は、式とPromiseの値の間の従属性を定義します。式の結果のための新しいPromiseを返し、新しい式がその値に依存できるようにします。circlesとsquaresの例に戻りましょう。ここでは、より単純な式で始めます。Promiseを使い、同期のsquareAreaをプログラムの並行記述に変えます。squareAreaは、side値にのみ依存しているので、よりシンプルなのです。10);コードの同期処理バージョンと比較すると、非常にノイズが多いのですが、この新バージョンはJavaScriptの実行順序と結びついていません。代わりに、その実行に働く唯一の制約は、私たちが記述した従属性です。最小限のPromise実行もう1つ、未解決の問題があります。記述した従属性から得た順序に沿うように実際にコードを走らせるにはどうすればよいのでしょうか。JavaScriptの実行順序に従わないのであれば、何か別のものが望みどおりの実行順序を提供しなければなりません。幸運にも、これまで使ってきた関数で簡単に定義できます。最初に値の表現方法と従属性を決めます。一般的なやり方は、このデータをcreatePromiseから返される値に追加することです。何かのPromiseはその値を代理できるものですが、必ずしも常に値が確定しているわけではありません。値は、fulfilを呼び出して初めてPromiseに配置されます。最小限の表現は次の通りです。somethingは、値nullを持った状態で生成されます。後の時点で、誰かがそのPromiseのためにfulfil関数を呼び出すと、その時点からPromiseはfulfilされた値を持つでしょう。Promiseがfulfilされるのは一度だけなので、その値は残りのプログラムの間、Promiseが持ち続ける値になります。value(nullは有効な値です)を見るだけでは、Promiseが既にfulfilされたかどうかの把握が不可能な場合があります。それを2度fulfilしてしまうリスクを避けるため、Promiseの値が入っているか否かの状態を追跡する必要があります。そのためには前の表現を若干変えます。"fulfilled"}また、depend関数によって作られた従属性も制御する必要があります。従属性が最終的に満たされることで、Promise内の値が満たされ、評価ができるようになりあます。1つのPromiseはその値に依存する多くの関数を持つことができるので、このための最小限の表現は次のようになります。something_else]}Promiseのための表現を決定したので、新たなPromiseを作る関数の定義から始めましょう。最初は"pending"状態です。};}シンプルな表現に決めたので、その表現のための新規オブジェクトの構築はかなり単純です。複雑な手順、Promiseへの従属性付与に進みましょう。この問題の解決法の1つは、Promiseのdependenciesフィールドの中に作られた全ての従属性を引き出し、Promiseを、必要に応じて計算を行うインタープリタに送り込むやり方です。これを実行すると、インタープリタが開始しない限り従属は動作しません。今回はこの方法によるPromiseの実装は行いません。多くの人がとるJavaScriptプログラムの一般的な記述方法に合わないからです3。1つの解決法は、「Promiseがpending状態にある間、本当に必要なのはPromiseの従属性を追跡することだけだ」という認識があれば分かります。Promiseがいったん完了すれば、関数を即座に実行できるからです。result;}depend関数は、値が出るまで待機してから、従属の表現の実行を扱います。従属性を付与するのが早過ぎると、その関数はPromiseオブジェクトの配列で終了してしまい、仕事は終わらないということになります。実行の2番目の要素では、値を取得した時点で従属性を実行させる必要があります。幸運にも、fulfil関数が使えます。Promiseに入れたい値と共にfulfil関数を呼ぶことによって、pending状態にあるPromiseをfulfilできます。これはPromiseの値が使えるようになる前に作られた全ての従属性を呼び出し、その他の実行を手配する好機です。全ての計算がいつも有効な値を生成するとは限りません。bや、a[0]といったいくつかの関数は部分的関数であり、従ってaやbの値が、それぞれある範囲にあるときのみ定義可能になります。部分的な関数を含むプログラムを書いて、関数で扱えない状況に当たった時は、プログラムの実行を続けられません。言い換えれば、プログラム全体がクラッシュするということです。プログラムに部分的な関数を組み込むより良い方法は、全域関数にすることです。先に定義されていなかった関数の部分を定義する方法です。一般的にこれは、関数が”成功”の形を扱える状況と、”失敗”の形を扱えない場合を考慮するということを意味します。この施策だけで、たとえ正しい値を生成できない計算に直面しても実行し続けるプログラムにすることができます。失敗の可能性のある部分それぞれで分岐させるのは合理的な処理方法ですが、必ずしも実用的ではありません。失敗し得る3つの計算を合成した場合、そのシンプルな合成に対して、少なくとも6種類の分岐を定義しなければならないのです。面白い事実プログラミング言語の中には、OCamlのように上述のスタイルのエラー処理を好むものがあります。起こっていることを明示するから、というのが理由です。一般に、関数型プログラミングは明示性を支持しますが、Haskellを含むいくつかの関数型言語は、エラー(やその他の事項)をより手際良く扱うため、Monad4と呼ぶインターフェースを使います。b))とだけ書いて、可能性のあるエラーを一度で処理したいものです。プログラミング言語はそのために様々な方法を備えています。C言語やGoのように、完全にエラーを無視、あるいは少なくとも可能な限り触れるのを延期する言語もあれば、Erlangのように、プログラムをクラッシュさせつつ、復旧させるツールを供給する言語もあります。見られるアプローチは、”エラー処理ハンドラ”を割り当て、失敗が起こりそうなコードを中断する方法です。JavaScriptはtry/catch文を通じてそのアプローチを可能にします。エラーをPromiseで処理するここまでのPromiseの形は、失敗を認めません。そのため、Promiseで起こる全ての計算は常に有効な値の結果を生成しなければなりません。bなどの計算を実行する時に問題になります。0の場合のように、bが0なら、その計算は正しい値を出せないからです。失敗の表現を極めて簡単に考慮するため、Promiseを修正します。現状、ここでのPromiseはpending状態から始まっており、可能なのはfulfilだけです。新しい状態、rejectedを追加すれば、Promise内に部分関数を作ることができます。成功する計算はペンディングの状態で始まり、最終的にfulfilledになります。失敗する計算はペンディングの状態で始まり、最終的にrejectedになります。今や失敗の可能性があり、Promiseの値に依存する計算もそのことに気をつけなければなりません。とり急ぎ、dependは、Promiseがfulfilledになった時に実行される式と、Promiseがrejectedになった時に実行される式を持つことになります。よって、新しいPromiseの表現は次のようになります。}]}Promiseは適切な値あるいはエラーを含み、それがfulfilledかrejectedかが確定するまでnullを含みます。この処理のため、従属性は適切な値とエラーの値に対して何を行うか知っていることも必要です。そこで従属性の配列を少しだけ変えます。表現の変更の他、depend関数も変えます。result;}最後に、エラーをPromiseに置く手段が要ります。reject関数を使いましょう。}}また、dependenciesフィールドを変更したため、fulfil関数をレビューしなければなりません。}}これらの新規追加を行ったところで、Promise上に失敗し得る計算を配置できるようになりました。最後のコード例は決してzPromiseを実行しません。c)は失敗するからです。これはまさに予測していたことですが、今はPromiseの中に計算を定義するたびに失敗の分岐を渡す必要があります。理想としては、同期処理のtry/catchで行ったのと同様に、必要な箇所にのみ失敗の分岐を定義したいところです。Promiseにとってこの機能性のサポートはたやすいのです。制御フローにおいてよく起こることですが、抽象化不可の場合のたびに、成功と失敗の分岐を定義するだけです。JavaScriptでは、if文やfor文を抽象化することはできません。それらは第二級制御フローメカニズムであり、編集、送り出し、変数への格納ができないためです。このPromiseは第一級オブジェクトなので、失敗と成功の具体的な表現を持っています。それらは作成された時点だけでなく、いつでも必要なときに検査、対話ができます。try/catchと似たものを手に入れるため、成功と失敗の表現を使って2つのことを行わねばなりません。計算が失敗した場合は、その値を筋の通る何らかの成功の形に変えます。MapやArray構造体からデータを取り戻す時に、初期値を使ったりします。計算が成功した場合は、その値を失敗に変え、失敗だった場合は、失敗を維持しなければなりません。前者は短絡演算子が使え、後者は失敗の伝播が使えます。d)の意味を取得できます。幸運にも、depend関数がこの仕事のほとんどを既に行っています。dependはその表現がPromise全体を返し、値だけでなく、Promiseが存在する状態の伝播も要求するからです。successful分岐だけを定義してPromiseが失敗した場合、値だけでなく失敗の状態も伝播させたいので、これは重要です。これらのメカニズムが既に適切だとして、シンプルな失敗の伝播のサポート、エラー処理と、失敗の時の短絡演算を行うには、2つの演算子の追加が必須です。chain、これは、失敗の際の短絡演算子ではなく、Promiseの成功した値に関して従属性を生成します。recoverは、Promiseの失敗した値に関して従属性を生成し、そのエラーから復旧させます。expression)}こうして、2つの関数を使って、先述の部分サンプルを単純化できます。Promiseを確定的に結合するPromiseの演算子の配列が一連の従属性を要求する一方、Promiseの並行的な結合はただ、Promiseが相互に従属性を持たないことだけを求めます。Circleの例における計算は、本来的に並行でした。radius式とMath.PI式は相互に依存しておらず、別々に計算されますが、circleAreaは両方に依存します。コードで表したのが下記です。Math.PI;print(circleArea);同じものをPromiseで表すと、次のようになります。Math.PI);ここで小さな問題があります。circleAreaAbstractionは2つの値に依存しますが、dependにできるのは1つの値に依存する式の従属性を定義することだけなのです。この制限の中で作業を進める方法はいくつかありますが、シンプルなものから始めます。dependが1つの式に対して1つの値を提供できるなら、クロージャの中で値を取得し、Promiseから1つずつ値を引き出すことができるはずです。これはいくつか暗示的な順序を生成しますが、並行処理への影響はそれほど大きくありません。})}これによって、circleAreaPromiseを下記のように定義できます。circleAreaAbstraction);3つの値に依存する式にwait3を、4つの値に依存する式にwait4などを定義できました。wait*は暗示的な順序を生成し(Promiseは特定の順序で実行されます)、差し込もうとしている値の数を事前に知っていることが条件です。そのため、例えばPromiseの配列全体を待ちたい場合には使えません(ただ、wait2とArray.prototype.reduceをそのために結合することはできます)。別の解決法は、Promiseの配列を受け取り、それぞれを可能な限り早く実行し、元のPromiseが持っている値の配列のためにPromiseを返すことです。このアプローチは、シンプルな有限状態機械(FSM)を実行する必要があるために少々複雑ですが、暗示的な順序はありません(JavaScript独自の実行を除きます)。それぞれのpromiseを実行することから始めます。result;}waitAllをcircleAreaAbstractionのために使う場合、次のようになります。Promiseを非確定的に結合するPromiseの結合の仕方を見てきましたが、ここまでは確定的に結合するしかありませんでした。2つの計算から速いほうを選びたい場合には役立ちません。2つのサーバ上で何かを探し、どちらか答えを返した方をとってもよいかもしれませんが、ここではとにかく最速の方法を使います。この作業のサポートとして、いくつか非確定性を導入します。2つのPromiseがある場合、最速で解決する方のpromiseの値と状態を取る演算が必要です。演算の背景にあるアイデアはシンプルです。2つのPromiseを並行に走らせ、最初の解決を待ち、それを結果としてのPromiseに伝播するのです。状態を維持するため、実装はいくらか複雑になります。両方のpromiseを並行して待ちます。promiseの結果/状態を伝播します。}}これによって、それらを非確定的に選んで演算の結合を開始できます。先のサンプルで見てみましょう。302つ以上のPromiseからの選択が可能です。b)は、基本的に速く解決する方に従ってaまたはbになるからです。b)と同様になるのです。...)))と入力するのは最善ではないので、シンプルなコンビネータを書きましょう。createPromise());}そして実際に使ってみます。searchB()])]);2つのPromiseから非確定的に選ぶ別の方法は、1番目のfulfilが成功するのを待つことです。ミラーのリストから正しいダウンロードリンクを見つける時、初めの失敗だけのために失敗するよりは、一番に得られるミラーからダウンロードし、全てが失敗の場合に失敗としたいはずです。これを取得するため、attempt演算子を書きましょう。`race`と同様、両方のpromiseを待機します。「どちらのpromiseがrejectを返したのか」を知る必要がある点です。エラーをトラックする必要があるからです。pendingであれば、エラーの収集を注意深く続けます。全てのエラーを収集した場合、結果のpromiseをrejectします。searchB())は、ただ最初のPromiseではなく、成功した最初のPromiseを返します。raceとは異なり、attemptはエラーを集めるので、自然には集約できません。いくつかのPromiseをattemptしたい場合は更なる記述が必要です。最後に、promiseの結合のために`attempt`を使います。2015はPromiseの概念をJavaScript用に定義しますが、これまで非常にシンプルな、しかし略式のPromiseの実装を見てきました。理由はECMAScript標準のPromiseは複雑過ぎ、基礎から概念を説明するのが難しいだろうと考えたからです。今はPromiseが何であるか、各アスペクトがどのように実装されるかを知っていますから、標準のPromiseに進むのは容易でしょう。Promiseの導入ECMAScript言語の新バージョンでは標準のPromiseをJavaScriptで定義します。この基準はいくつかの方法で導入してきた最小限のPromiseの実装とは異なり、より複雑ですが、より実用的で簡単に使えます。以下のテーブルは各実装の相違点をリストアップしています。Promise(...)、それを変形させる.then(...)です。その動作には、ここまでで書いた演算子と比較していくつかの違いがあります。Promise(f)は新規Promiseオブジェクトを導入し、特定の値で成功か失敗をする計算を行います。成功か失敗の動作は、想定どおり、関数fに渡された2つの関数引数によって取得されます。g)は値のための穴を持つ式とPromise内の値の間に従属性を作る演算で、depend演算に似ています。fとg共に、オプションの引数で、Promiseを供給されない場合、その状態で値を伝播します。dependとは異なり、.thenは複雑な演算で、Promiseの利用を簡単にしようとします。.thenに渡された関数実引数はPromiseか、一般の値を返します。その場合、これらは自動的にPromiseに置かれます。1);これらは、先の記述と比べ、Promiseを使ったコードを簡潔に読みやすくします。side;print(squareArea);複数の値に並行に従属させるのは、Promise.all演算です。先に見たwaitAll演算に似ています。print(circleArea));エラーと成功の伝播は.itself演算そのものが扱います。.catch演算は、成功の分岐を定義することなく.thenを呼び出す簡潔な方法です。.thenの分析.thenメソッドはいくつかの点で先に書いたdepend関数とは違っています。.thenは結果値と複数の計算の従属関係を定義する1つのメソッドですが、同時にメソッドの使用を大多数のケースで容易にしようとします。このことが.thenを複雑なメソッドにしていますが5、先に見たからくりとこの新しいメソッドを関連付ければ分かりやすいでしょう。.thenは自動的にただの値を引き上げる先に作ったdepend関数はPromiseの領域で作動しました。従属の計算がPromiseそのものを返すために、Promiseを返すように作られています。.thenにはこの条件がありません。従属の計算が42といったただの値を返した場合、.thenはそれを、値を含むPromiseに変換します。本来、.thenは、必要に応じてただの値をPromiseの領域に引き上げるものです。単純化したタイプのdepend関数と比較します。単純化したタイプの.thenメソッドです。唯一depend関数でできることは、何かのPromiseを返すこと(それから、同じ何かを結果のPromiseに持たせること)ですが、.then関数は簡便性のため、ただの値を(promiseでラップすることなく)返すことを許容しています。Promiseが一般的なユースケースでの用法を簡素化しようとしているもう1つの方法は、Promiseのネストの禁止です。.thenメソッドを持つすべてを同化することによるもので、同化させたくない場合は問題になり得ます6。なければ戻り値のタイプのマッチングを考えずに済むので便利です。この特徴ゆえ、.thenメソッドに、非従属タイプに認識可能なタイプを与えることは不可能です。それは大まかに説明すると以下のような形になります。1)))は、下記と等価です。1))これは、Promise.resolveでも強制されますが、Promise.rejectでは実行されません。.thenは例外を具象化する.thenメソッドを通して付加した従属の計算を評価する間、同期的に例外が起こった場合、その例外はrejectされたPromiseとして見つけられ、具象化されます。これが本質的に意味するのは、「.thenメソッドを通してPromiseの値に付加した全ての計算は、暗示的にtry/catchブロックにラップされたかのように扱わねばならない」ということです。null());上記は、次と等価です。}});Promiseのネイティブの実装はこれらを追跡し、処理がされていないものを報告します。Promiseにおいて”取得されたエラー”の構成に関する仕様はないので、エラーリポートのされかたは開発ツールごとに異なります。Chromeの開発ツールは、rejectされたPromiseのインスタンスの全てをコンソールに出力するため、誤検知を表示することがあります。Promiseは非同期的にそれを行います。適切な手段、.thenメソッドを使わなければ、Promiseの値への依存は難しいということです。従って、次のコードは動きません。2015では重視されていません。全ての実装が適切な末尾再帰をサポートすることが条件になっているからです7。Promiseと相性が悪いケースとは?Promiseは並行処理の基本形として良く機能しますが、継続渡しスタイルほど一般的ではなく、全てのケースにおいて最善の基本形でもありません。Promiseは最終的には計算される値のプレースホルダなので、これらの値そのものを使うコンテキストでのみ意味をなします。promiseは値のコンテキストにおいてのみ意味をなすPromiseをそれ以外の何にでも使おうとすると、メンテナンスが困難で、読みにくく、拡張もししづらい、非常に複雑なコードベースになってしまいます。以下はPromiseを使うべきでない例の一部です。特定の値の計算の進行状況を通知すること。promiseは値そのものと同じコンテキストで使われるので、文字列そのものを与えられて特定の文字列計算の進捗状況を知ることができないのと同様、Promiseでもそれは不可能です。このため、どれだけのファイルがダウンロードされたかを知りたい場合は、Eventなどを別に用意するのをおすすめします。時間をかけて複数の値を生成すること。Promiseは1つの結果となる値のみを表現できます。経時的に複数の値が生成され得る場合(非同期イテレータと同義)、Stream、ObservableまたはCSPチャネルなどを要するでしょう。これも、Promiseは順番に実行できないことを意味します。Promiseを使うと、そのために値を提供する計算も始まっているからです。(co)monadを使うことができます。まとめPromiseは結果の値を扱う優れた方法です。非同期で計算される値に依存するプロセスの合成と同期ができるようになります。2015標準のPromiseには、プロセスをクラッシュさせるエラーを自動的に具象化するなど、特有の課題がありますが、前述の問題を十分適切に扱えるツールです。実際に使うかどうかは別として、それらの特徴と動作を理解するのが大切です。今後は全てのECMAScriptプロジェクトにさらに普及していくと考えられるからです。WirfsがJavaScriptのPromiseの標準を定義。Smolkaがプログラミング言語Aliceを紹介。FutureとPromiseで並行処理をサポート。Jonesが、プログラミング言語Haskellの意味論を分かりやすく説明。選択などのプロセスの並行コンビネーションを説明。Wadlerが、関数型言語においてモナドがどのようにエラーを扱うかなどを説明。Promiseのシーケンス処理とエラーの扱いはモナドの形式に非常に似ている。2015標準ではPromiseはモナドインターフェースを実装しない。追加資料ブログポストのソースコードこのブログポストのコメント付きソースコード全て。仕様に準拠するPromiseの実装も含む。Promise標準が持つ問題を、複雑さ、エラーの扱い、パフォーマンスの面から議論する。Lonsdorfによる、JavaScript関数型言語の導入本。Coglanが、プログラムの実行順序を記述するための継続渡しスタイルとPromiseを比較。Hickeyが語るデザインのコンテキストにおける「シンプル」「簡単」は、常にプログラミングにもあてはまる。Callsを使うメリットの説明。Meijerが、Observableの概念を用いてRxのEventベース、非同期の計算のコーディネートと管理について議論する。Hallidayによる、Streamを用いてNode.jsプログラムを書く基本。Mightが、継続渡しスタイルを使い、JavaScriptのスレッドが中断されない計算を扱う方法を説明。プログラミング言語のコンテキストで議論する。comonadを使ったC#の非同期計算、その解決法と他のメソッドとの関わりを説明。2015標準のPromise、ES2015を実装していないプラットフォームのためのポリフィル。Promises/A、Promises/A+とその他の一般的なPromise式では、Promiseの値を抜き出すことはできません。Nashornなど一部のJavaScript環境では、値の抜き出しをサポートするPromiseの実装へのアクセスを持ち得る可能性はあります。JavaのFutureが一例です。Promiseから未計算の値を抜き出すには、値が計算されるまでスレッドを中断しなければなりません。ほとんどのJS環境はシングルスレッドのため、それは機能しません。「ラムダ抽象化」はある式の中で抽象化を行う匿名の関数にラムダ計算が与える名称です。JavaScriptの匿名関数はLCのラムダ抽象化と同等ですが、JavaScriptではさらに自らの関数に名前をつけることもできます。“計算の定義”と”計算の実行”の分離には、Heskellプログラミング言語が役立ちます。Haskellプログラムは、IOデータ構造体まで評価する巨大な式に過ぎません。この構造体はいくらかこの記事で定義したPromise構造体に似ています。そこではプログラム内の計算間の従属性が定義されるだけです。HaskellではプログラムはタイプIOの値を必ず返し、分離されたインタープリタに渡されます。インタープリタはIO計算を行い、それが定義した従属性を尊重することしか知りません。JSのために似たものを定義することは可能かもしれません。それを行うと、全てのJSプログラムは結果としてPromiseに至るただ1つの式になるでしょう。そのPromiseは、Promiseとその従属性の実行の仕方を認識している別の要素に渡されるはずです。Promisesのサンプルディレクトリを見てください。モナドは、シーケンス処理に頻繁に使われるインターフェースです。次のような演算の構造体として書かれます。print(2))の使用として考えることが可能です。Hickeyが言うところの”複雑”と”容易”です。.thenは確かに簡単なメソッドです。シンプルな概念で、一般的なユースケースに使えます。要は、.thenは多くの作業を行い、そこには大量の重複があるということです。一方で、シンプルなAPIはこれらの分離した概念を、.thenメソッドと一緒に使える別の関数に移動させます。.thenメソッドはPromiseに見える全てのものの状態と値をどうかします。歴史上、これはインターフェースチェックを通して行われます。オブジェクトが‘“.thenメソッドを供給したかどうかをチェックするだけで、それには、内包する.thenメソッドがPromiseの.then“`メソッドに準拠していない全てのオブジェクトが含まれます。標準のPromiseが、既存のPromise実装との下位互換に制限されていなければ、より信頼のおけるテストができるでしょう。その際はインターフェースのSymbolや、それに類するものを使います。Callsは末尾の呼び出しが一定のスタックで起こることを保証します。プログラムや計算が末尾再帰で占められており、スタックが膨らまない限り保証されるので、そのようなコードではスタックオーバーフローエラーが起こり得ません。Callsにより、言語は通常の関数呼び出しの負荷を処理する必要がないため、上のようなコードの速度は上がります。ツイート翻訳に対するフィードバックがございましたら、メールまたはGitHubのIssueよりお寄せください。Ltd.リクルートグループサイトへ。