JavaScriptの非同期処理
同期処理と非同期処理
時間の掛かる処理があります。例えば、インターネット通信や、ファイルの読み込みといった類の処理です。これらの処理を一般的な方法で実装しようとすると、その処理を行う間、ユーザーからの入力が受けられない状態になってしまいます。これが俗に言う、フリーズした
状態です。
幸いなことに、JavaScript では、時間の掛かる処理の待ち時間を、他の処理のために充てることができるような仕組みが用意されています。これを、非同期処理といいます。JavaScript における非同期処理は、歴史的にはコールバックを用いて処理されます。
setTimeout(() => {
console.log("Text 1");
}, 1000);
console.log("Text 2");
setTimeout
関数は、2 つの引数をとり、第 1 引数で指定された関数を、第 2 引数で指定された時間(ミリ秒)後に実行します。このように、システムによって処理の完了後に呼び出される関数を、コールバック関数と呼びます。上記の例では、コールバック関数の実行は後回しにされ、非同期的に実行されます。このため、実行結果はText 2
、Text 1
の順となります。
それでは、setTimeout
関数を用いて、1 秒毎に異なるメッセージを表示させることを考えてみましょう。すぐに思いつくのは、以下のようなコードです。
setTimeout(() => {
document.write("Text 1");
setTimeout(() => {
document.write("Text 2");
setTimeout(() => {
document.write("Text 3");
setTimeout(() => {
document.write("Text 4");
}, 1000);
}, 1000);
}, 1000);
}, 1000);
くどいですね。一昔前の JavaScript では、上記コードのように、コールバック関数が大量に使用された結果、インデントが非常に深くなるという事態に陥っていました(コールバック地獄)。
この状況を解決するために生まれたのが、Promiseと呼ばれる考え方です。Promise を用いると、上記のコードは以下のように書き換えることができます。
function mySetTimeout(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
mySetTimeout(1000)
.then((value) => {
document.write("Text 1");
return mySetTimeout(1000);
})
.then((value) => {
document.write("Text 2");
return mySetTimeout(1000);
})
.then((value) => {
document.write("Text 3");
return mySetTimeout(1000);
})
.then((value) => {
document.write("Text 4");
return mySetTimeout(1000);
});
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 function promiseTest() {
await mySetTimeout(1000);
document.write("Text 1");
await mySetTimeout(1000);
document.write("Text 2");
await mySetTimeout(1000);
document.write("Text 3");
await mySetTimeout(1000);
document.write("Text 4");
}
promiseTest();
async
キーワードを指定した関数の中でawait
文が実行されると、その関数は Promise が完了(resolve または reject)されるまで実行が中断されます。Promise が resolve された場合にはその値がawait文の戻り値となります。
なお、await文を使用する関数には、asyncキーワードを付与する必要があります。asyncキーワードが付与された関数は、自動的に戻り値が Promise 型となります。