はじめに
JavaScriptを用いる際に理解しておくべき非同期処理についてまとめました。
非同期処理
- JavaScriptのコードは、基本的にメインスレッド(UIスレッド)で実行される
- メインスレッドでは、画面の描画等の処理も実行される
- ゆえに、メインスレッドに時間のかかる処理(サーバーへのHTTPリクエストなど)がある場合、そこで画面がフリーズしてしまう
- 非同期処理を用いれば、時間のかかる処理と並行してその他の処理を実行できる
- そのため、画面フリーズの問題を解決できる
- 非同期処理は、基本的に並行処理であって、それらはメインスレッドで実行される
Promise
- Promiseは、現代のJavaScriptにおいて非同期処理を行う際の基礎となるもの
-
fetch()
APIなどのPromiseベースのAPIでは、返値としてPromiseオブジェクトを返す - 非同期処理を実行すると、すぐにPromiseオブジェクトが返されて、次の処理へ移る
- Promiseオブジェクトの
then()
メソッドにハンドラー関数を渡すと、非同期処理が成功した場合にそのハンドラー関数が実行される - Promiseオブジェクトの
catch()
メソッドにハンドラー関数を渡すと、非同期処理が失敗した場合にそのハンドラー関数が実行される。つまり、エラー処理を行う -
then()
メソッドもPromiseオブジェクトを返すため、.
(ドット)で繋いで非同期処理を連続して行うことができる。非同期処理は、その順番で完了されていく -
then()
メソッドの連鎖の最後にcatch()
メソッドを繋ぐと、連鎖の中のいずれかの非同期処理が失敗した場合にエラー処理をすることができる
fetch()を使った例
const fetchPromise = fetch(
'https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json'
);
fetchPromise
.then(response => {
if (!response.ok) {
throw new Error(`error: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(data[0].name);
})
.catch(error => {
console.error(`処理が失敗しました。: ${error}`);
});
console.log('処理の開始');
- 上記の例では、
fetch()
が呼び出されると、fetchPromiseにPromiseオブジェクトが格納され、非同期処理が始まる。この後の非同期処理(HTTPリクエスト及びjson()
メソッド)は時間がかかるので、先にconsole.log('処理の開始');
が完了することに注意。その後、非同期処理が順に完了して行き、取得したデータの内容が表示される。なお、URLをいじってリクエストを失敗させれば、catch()
メソッドの内容が実行される。
Promiseが登場する前
- Promiseが登場する前には、コールバック関数を用いて非同期処理を実装していた
- しかし、コールバック関数を受け取るコールバック関数が必要となるなど、ネストが深くなりがちであったため、コードがとても分かり辛いものとなっていた(コールバック地獄)
- また、ある非同期処理の外側からその非同期処理のエラー処理をすることはできないため、各非同期処理ごとにエラー処理を記述する必要があり、これもコードが分かり辛くなる原因となっていた
async/await
-
async
キーワードを用いると、Promiseによる非同期処理をさらにシンプルに動作させることができる。 - 関数の開始時に
async()
を追加すると、その関数は非同期関数になる - 非同期関数の内部では、Promiseを返す関数を呼び出す前に
await
キーワードを使用することができる。これは、Promiseの結果が決定するまでその関数で処理を待たせ、Promiseの結果を返すか、失敗時の例外を発生させる -
async
及びawait
を用いることで、あたかも通常の同期処理のコードのように記述することができる。エラー処理も通常のtry-catch文のように書ける。
async/awaitを用いた例
async function fetchProducts () {
try {
const response = await fetch(
'https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json'
);
if (!response.ok) {
throw new Error(`error: ${response.status}`);
}
const data = await response.json();
console.log(data[0].name);
} catch (error) {
console.error(`処理が失敗しました: ${error}`);
}
}
// これは非同期関数であり、時間がかかる処理なので、この次のconsole.logの処理が先に実行される。
fetchProducts();
console.log('処理の開始');
参考文献
mdn 非同期 JavaScript
JavaScript Primer 非同期処理:Promise/Async Function