JavaScriptのPromise
は、非同期処理が終わるまで、その処理の代理人としてがんばってくれるオブジェクトである。おかげで、非同期処理を同期処理のようにシンプルに書ける。
導入理由
JavaScriptにPromise
が導入されたのは、非同期処理をわかりやすく書きたい、という強いモチベーションがあったからだ。古き良きsetTimeout()
のような関数(正確にはwindow
オブジェクトのメソッド)であれば、コールバックによる非同期処理でもさしたる問題はなかった。
しかし、Node.jsランタイムの登場で非同期処理の活用シーンが爆発的に増加し、従来のコールバックによる非同期処理ではソースがえらく分かりづらくなった。
例えば以下のようなイメージである。
引用元:Callback Hell
いわゆるコールバック地獄と呼ばれるコードだ。
ネストは浅い方が見通しが良い。書くのも楽だ。先行言語にはすでにFuture
あるいはPromise
と呼ばれる仕組みがある。それらを参考に直感的に書ける独自のPromise API
を実装するNode.jsライブラリなどが登場した。
これは標準にすべきだ、という潮流が生まれ、現在に至る。そんな感じだろう(あれこれ読んだ上での推測)。
作成方法
JavaScriptでは、new
キーワードを使ってPromise()
コンストラクタを呼び出すことでPromise
オブジェクトを作成する。引数には、resolve
、reject
という2つの引数を取る関数を渡す。余談だがこの関数はexecutor
と呼ばれる。実行したい非同期処理は、executor
の中に書く。
resolve
とreject
は、非同期処理が終わった場合に、処理を次に進めるための関数オブジェクトである。処理が成功した場合はresolve()
関数にその値を渡し、失敗した場合はreject()
関数にその理由(普通はError
インスタンス)を渡して実行する。
上記のサンプルコードでは、1/2の確率でどちらかが実行される。
値は空でもいいが、嘘だった。TypeScriptの場合、型を指定せずに呼び出すと警告が出る。例えば空で呼び出す場合は以下のようにする。
resolve()
あるいはreject()
のいずれかを呼び出さないと、Promise
が抱える非同期処理は終わらない。果たされない約束として電子の海に留まることになる。なお、executor
が返す値は無視される。可哀想。
使い方
Promise
インスタンスは、いわば非同期処理の代理人(Proxy)である。作っただけでは何も得られない。この代理人に、処理が終わったらどうするか指定しなければならない。
そのために、同インスタンスのthen()
メソッド、あるいはawait
キーワードを使って、結果を得る処理を書く必要がある。
then()メソッド
Promise
インスタンスのthen
メソッドは、2つの関数を引数に受け取る。1つ目はresolve()
関数が実行された場合に実行されるもので、2つ目はreject()
関数が実行された場合に実行される。
Promise
インスタンスのメソッドにはthenの他にcatch()とfinally()があるがここでは簡便のために割愛する。勘弁だ。どちらかというとエラーハンドリングの領域だし。
結局コールバックやないかい、と感じるか、わかりやすいと感じるかは、個人の感性によるところかと思う。
awaitキーワード
await
キーワードを使うと、より同期処理に近い形で非同期処理を書くことができる。
このようにawait
をつけるだけで、非同期処理の代理人はデキる紳士のようになる。具体的には、resolve()
関数に渡された結果をそのまま返してくれる。reject()
が実行された場合は例外が投げられるので、なるべくならtry{}catch{}ブロックの中で実行した方が無難だ。
また、await
キーワードはモジュールのトップレベルか、async
キーワードをつけて定義された関数の中でしか使えない。加えて、await
で待機できるのは、最も近いasync
キーワード関数の中だけである。
つまりasync
関数の外側の処理は待機されない。注意したい。