JavaScriptの非同期処理
同期処理と非同期処理
時間の掛かる処理があります。例えば、インターネット通信や、ファイルの読み込みといった類の処理です。これらの処理を一般的な方法で実装しようとすると、その処理を行う間、ユーザーからの入力が受けられない状態になってしまいます。これが俗に言う、フリーズした
状態です。
幸いなことに、JavaScriptでは、時間の掛かる処理の待ち時間を、他の処理のために充てることができるような仕組みが用意されています。これを、非同期処理といいます。JavaScriptにおける非同期処理は、歴史的にはコールバックを用いて処理されます。
setTimeout
関数は、2つの引数をとり、第1引数で指定された関数を、第2引数で指定された時間(ミリ秒)後に実行します。このように、システムによって処理の完了後に呼び出される関数を、コールバック関数と呼びます。上記の例では、コールバック関数の実行は後回しにされ、非同期的に実行されます。このため、実行結果はText 2
、Text 1
の順となります。
それでは、setTimeout
関数を用いて、1秒毎に異なるメッセージを表示させることを考えてみましょう。すぐに思いつくのは、以下のようなコードです。
くどいですね。一昔前のJavaScriptでは、上記コードのように、コールバック関数が大量に使用された結果、インデントが非常に深くなるという事態に陥っていました(コールバック地獄)。
この状況を解決するために生まれたのが、Promiseと呼ばれる考え方です。Promiseを用いると、上記のコードは以下のように書き換えることができます。
Promiseに対応する関数やメソッドの作成
setTimeout
関数は、昔ながらのコールバック関数を用いた形式となっています。そのため、Promiseを用いてsetTimeout
を利用するためには、setTimeout
をPromiseに対応させるための関数(ラッパー関数)を作成する必要があります。
Promiseに対応する非同期処理関数は、Promiseクラスのインスタンスを返します。Promiseクラスのコンストラクタは、ただ1つの引数をとります。この引数はシステムによって呼び出される関数で、呼び出し可能な二つの引数(resolve
とreject
)をとることができます。非同期処理はその関数の中で実行し、正常に終了した時点でresolve
を、異常終了した場合はreject
を呼びます。
resolve
はただ一つの引数を取り、非同期処理の結果を渡します。reject
はただ一つの引数を取り、処理の失敗の理由を渡します。
Promiseを用いた非同期処理に対応する関数の使い方
Promiseに対応する非同期処理関数により得られたPromiseクラスのインスタンスは、then
メソッドを持ちます。then
メソッドはただ一つの引数を取り、この引数は非同期処理の終了時にシステムによって呼び出される関数です。この関数はただ一つの引数を取り、非同期処理の結果を受け取ります。
また、この関数の中で別のPromiseを返すと、そのPromiseがresolveされた時点で、then
メソッドの返り値として得られるPromiseがresolveされます。
Async / Await文
Async / Await構文を用いると、Promiseを用いた非同期処理をさらに簡潔に記述することができます。
async
キーワードを指定した関数の中でawait
文が実行されると、その関数はPromiseが完了(resolveまたはreject)されるまで実行が中断されます。Promiseがresolveされた場合にはその値がawait文の戻り値となります。
なお、await文を使用する関数には、asyncキーワードを付与する必要があります。asyncキーワードが付与された関数は、自動的に戻り値がPromise型となります。
await
文のの戻り値を利用したサンプル
await
文は、式の中で使用することもできます。この場合、式の評価が一時中断され、Promiseがresolveされてから続きが評価されます。
アロー関数にも、直前にasync
キーワードを付与することにより、async
関数として扱うことができるようになります。上の例では、async
キーワードをつけて生成した無名関数をその直後で実行させています。JavaScriptにおいて、await
キーワードはasync
関数内のみでしか使用できないため、このような記述をよく見かけます。
note
JavaScriptのこの制約をなくすための提案がなされており、いずれは実現されるものと思われます。
課題
input
要素上でキーが押されるとresolveされるPromiseを返す関数getKey
を定義してください。以下のように使用できることを想定しています。