JavaScriptのPromise

JavaScript の非同期処理の記述を完結にする Promise の使い方について紹介します。TypeScript で async/await を使う際にも重要な事項です。


筆者は C# や C++ がメインのため、ファイル読み込みなどは同期処理できないと何とも書きづらい性分の人間です。そういった人からすると、JavaScript の非同期的な書き方(コールバック地獄?)はつらいのです。ただ、この悩みは多くの方々が持っているようで、すでに解決策が提案されているようです。それが、ECMAScript2015(ES6) から導入された、Promise オブジェクトです。TypeScript 2.1 から、es5 をターゲットした場合でも async/await が使えるようになりました。ただ、この async/await は、Promise のシンタックスシュガーのため、そのあたりの構文を使いこなすためにも、Promise の使い方は理解しておく必要があります。

Promise が使える環境

さて、Promise は ES6 の仕様ということで、現在の対応状況について調べてみます。なお、この記事ではブラウザ上で動作させることを前提に進めていきます。

Can I use …によると、この記事を執筆時点で、IE11 以外のブラウザでは大体対応しているしているようです。IE11 でも Promise が使えるようなライブラリが多く存在しているようで、実質使っても問題ないといってよいでしょう。IE11 で使う場合は以下のライブラリが良いようです。

このページの中ごろに使い方が参照の仕方が書いてあります。以下のコードを、ページの処理ソースの前に読み込んでおけば問題ないようです。「7.0.4」はバージョン番号のため、更新されると思います。

TypeScript の async/await を使った場合、IE11 では上記のライブラリを読み込んでおかないとうまく動作してくれませんでした。(問題ないという記事も見かけたため、もう少し調べる必要がありそうです)

使い方

Promise を使うといろいろと便利なことができるようですが、ひとまず、非同期のコールバック処理を同期的に書くような書き方について調べていきたいと思います。

1 秒(1000 ミリ秒)ごとに 3 つのメッセージを出力するような以下の処理を題材としていきます。

これを実行すると、以下のような出力が得られます。

さて、先のコードを Promise に書き換えると以下のようになります。

なんだか記述量が増えてしまいましたが、入れ子はすこし解消された気がします。

Promise オブジェクトの動作

簡単な例として、コンソールに文字列を出力してみます。

Promise の引数には、実行する関数を渡します。関数の引数には上記のスクリプトを実行すると、以下のように出力されるはずです。

Promise は、オブジェクト生成時に渡された関数を実行します。次に、以下のように、Promise オブジェクトの then に「Message2」と出力する関数を渡してみます。

これを実行すると、

としか、実行されません。

then 以降を実行できるようにするためには、Promise のコンストラクタに渡した関数内で適切な処理をする必要があります。Promise に渡す関数には、引数として、成功した際に呼び出す関数が渡されます。それを受け取り、呼び出す必要があります。Promise に最初に渡した関数を以下のように書き換え、その関数内で呼び出します。

引数の「resolve」とその呼び出し「resolve();」がポイントです。このスクリプトを実行すると以下のように表示されます。

ちなみに、p.then( ~ ); の文は、別に処理を待っているわけではありません。そのため、例えば以下のように書くと、

以下のように出力されます。

このように、Promise オブジェクトが処理をブロックするわけではありません。1 → 2 → 3 のようにしたい場合は、then をつなげていきます。

then の振る舞いは、Promise オブジェクトを返した場合とそうでない場合で若干挙動が異なるのか、もう少し調べる必要はありますが、ひとまずこういった形で使えるようです。

then 以降の関数に引数を渡す

resolve 関数を呼び出す際に引数を渡すと、then に渡した関数の引数として受け取ることができます。

このスクリプトを実行すると

と出力されます。これらを利用すれば、コールバック地獄なしに、同期的に書けそうです。

Promise を使った書き方のまとめ ( テンプレート )

Promise の使い方は以下の通りになると思います。

  • 非同期処理をする関数は、Promise オブジェクトを返す。
  • 次の処理に移行する際には第一引数の関数を呼び出す。
  • 非同期処理をする関数を、then でつないでいく。

コードにすると…

この場合、最後の asyncFunc3 は、Promise オブジェクトを返す必要はありませんが、一応です。

ちなみに、Promise では、処理に成功した場合と失敗した場合とで処理を変更することもできます。