はじめに
JavaScriptに関する「非同期処理」「Promise」「async/await」について簡単にまとめました。
初心者の方にとっては少しとっつきにくいお話だと思いますが、こんな感じか〜くらいに理解してもらえれば嬉しいです。
概要
- 非同期処理とは(同期処理との比較)
- Promise
- async/await
非同期処理とは
非同期処理は字の如く「同期的ではない処理」となります。
同期的な処理とは、
- Aの処理
- Bの処理
- Cの処理
と処理が順番に書かれている場合、上から順に処理が実行され、AとBの処理が完了した後にCの処理が実行されるようなものになります。
例えば、
console.log('A');
// 3秒後に実行する処理
setTimeout(() => {
console.log('B(3秒後に実行)');
}, 3000);
console.log('C');
このような処理があった時、A→3秒後にB→Cの順に処理されることが理想ですが、実際に動かしてみると
A
C
B(3秒後に実行)
というような順番で処理されています。
JavaScriptは非同期言語であり処理を待つことができないため、前の処理に時間がかかった場合は、その処理の完了を待たずに後続処理が行われてしまうことが理由です。
ですが、後述するPromise
やasync/await
を使うことでこのような非同期処理を同期的に扱うことができるようになります。(もちろん非同期的に処理させることも可能です)
Promise
Promiseは日本語に訳すと「約束」となりますが、Promiseを使うことで処理の順序や後続処理をあらかじめ指定(約束)できるものだと考えてもらえれば大丈夫です。
下記のコードでは、new Promiseの第一引数のコールバック(ここではメソッドの引数に渡す関数のこと)の処理でsetTimeout()
を実行してます。
処理が完了したことを知らせるために処理の最後にresolve()
を実行します。
こうすることで、resolve()が実行された後に、メソッドチェーンで処理されているthen()
メソッドの中身が実行されるようになります。
console.log('A');
// お約束を取り付けたい処理にPromise
new Promise((resolve) => {
// 3秒後に実行する処理
setTimeout(() => {
console.log('B completed.');
// 処理の完了を知らせる
resolve('');
}, 3000);
}).then(() => {
// Bの処理の完了を待ってから実行される処理
console.log('C');
});
A
B completed.
C
Promiseには状態がある
Promiseには3つの状態があります。
- pending
- 未解決で、処理が完了するのを待っている状態
- resolved
- 解決済みで、処理が問題なく完了した状態
- rejected
- 拒否されたことで処理が失敗してしまった状態
以下に3パターンのコードを記述します。
pending
const promise = new Promise((resolve) => {});
console.log(promise);
この状態だとまだPromiseに対して何もしていないため、pendingになります。
resolved
const promise = new Promise((resolve) => {
resolve('ここのメッセージが出るよ');
}).then((resolve) => {
console.log(resolve);
});
結果
ここのメッセージが出るよ
resolveさせることができました。
また、resolve()メソッドの引数に渡した値は、then()のコールバック関数の引数として受け取ることができます。
rejected
Promiseインスタンスの第二引数にはreject
を指定することができます。
const promise = new Promise((resolve, reject) => {
reject('エラー');
}).then((resolve) => {
console.log(resolve);
}).catch((reject) => {
console.log(reject);
});
結果
エラー
rejectさせることができました。
余談ですが、Promise.all()
というメソッドがあり、引数にPromiseの配列を渡すことで、全ての処理がresolveしてから次の処理に進むといったことを実現できます。
const promiseA = new Promise((resolve) => {
setTimeout(() => resolve('promiseA completed.'), 3000);
});
const promiseB = new Promise((resolve) => {
setTimeout(() => resolve('promiseB completed.'), 7000);
});
const promiseC = new Promise((resolve) => {
setTimeout(() => resolve('promiseC completed.'), 5000);
});
Promise.all([promiseA, promiseB, promiseC])
.then((resolve) => {
console.log(resolve);
});
結果も各Promiseのresolveを配列で受け取れます。
async/await
- async
- 非同期関数を定義する時に使用。関数の頭につけるとPromiseを返す関数を定義することができる。
- await
- Promiseオブジェクトが値を返すのを待つための演算子。
- async関数内でしか使用できない。
async
下記のように非同期関数を定義する時に使用し、戻り値の型がPromise型になります。
Promise型はジェネリクスとなっているため、resolve()の引数に渡す型と揃えるようにします。
const fetchData = async (): Promise<string> => {
return new Promise((resolve) => {
resolve('Data fetched!');
});
}
上記で定義したfetchData
というasync関数を実行していきたいと思いますが、
まずはawait
をつけずに実行してみます。
const data = fetchData();
console.log(data);
Promiseで返ってきた値をそのままthen()
することで、resolveさせることができます。
const data = fetchData();
data.then((resolve) => console.log(resolve));
では次に、await
をつけて実行します。
const data = await fetchData();
console.log(data);
awaitをつけて実行することで、Promiseによって約束されている戻り値がそのまま返ってくるため、data
変数から直接参照することができました。
こんなようにasync/awaitを使うことで、Promiseを使用した処理でthen()などを使用せずに、非同期処理を簡潔に書くことができます。
try catch
によるエラーハンドリングも書き方にいくつかパターンがあり、
try {
await fetchData();
} catch (error: Exception) {
console.log(error);
}
といったシンプルな書き方や、
await fetchData().catch((error) => {
console.log(error);
})
こんな感じで非同期処理ごとにエラーハンドリングを定義することもできます。
この辺は好みですね。
最後に
個人的にも曖昧だった非同期処理について見直す良い機会になったと思います。
最後まで読んでいただきありがとうございました。
参考記事
・async/await 入門(JavaScript)
・【ES6】 JavaScript初心者でもわかるPromise講座
Promiseの状態についてもっと詳しく知りたい方は↓
fulfilled, rejected, settled... Promiseの用語、全部分かりますか?