非同期処理とは
ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと。
そうすることで、時間のかかる処理を並行で処理し、その処理の完了を待たずに次の処理ができる。
JavaScriptはシングルスレッドなため、通常では並列で複数の処理ができない。そのため効率的に処理できるように考えられた仕組み。
コールバック関数とは
別の関数(高階関数)に渡すための関数。
// 例: setTimeout(callback, ms)
setTimeout(function() {
console.log(5);
}, 1000);
setTimeout(() => { // アロー関数ver.
console.log(5);
}, 1000);
// 1秒毎に数値を表示してカウントダウンしたいが、このコードでは1秒後にすべて実行される
setTimeout(() => console.log(5), 1000);
setTimeout(() => console.log(4), 1000);
setTimeout(() => console.log(3), 1000);
setTimeout(() => console.log(2), 1000);
setTimeout(() => console.log(1), 1000);
setTimeout(() => console.log(0), 1000);
setTimeout(() => {
console.log(5);
setTimeout(() => {
console.log(4);
setTimeout(() => {
console.log(3);
setTimeout(() => {
console.log(2);
setTimeout(() => {
console.log(1);
setTimeout(() => {
console.log(0);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
Promiseとは
JavaScriptにおいて、非同期処理の操作が完了したときに結果を返すもの。
(後で返す という「約束」)
コールバック地獄を避けるために考えられた仕組み。
ステータス
Promiseはステータス(状態)を持つ。
- pending (保留中)
- fulfilled (達成された)
- rejected (却下された)
resolve, reject
Promiseの引数に設定するコールバック関数。
- 処理が問題なく完了すればresolveが実行される。 (ステータスはfulfilled)
- 問題があればrejectが実行される。 (ステータスはrejected)
then(), catch()メソッド
次に処理したいことを登録しておく。
- then() : resolve()された場合の値を取得
- catch() : reject()された場合の値を取得
getImage(file) // 画像ファイルの読み込みをする
.then(image => compressImage(image)) // 画像を読み込んだら、圧縮する
.then(cImage => saveImage(cImage)) // 圧縮が完了したら、その画像をDBに保存する
.then(result => console.log(result)) // 保存した結果を出力する
.catch(err => {throw new Error(err)})
このプログラムのどこでエラーが発生したとしても、エラーをキャッチする。
then()はfulfilled以外の場合は全てスルーして次の処理に渡すため。
エラーの場合、ステータスはrejectedになっているので、全てのthen()はこれをスルーする。最後に残ったcatch()だけがこれを掴むことができる。
Async と Await
コールスタックとキュー
同期処理、非同期処理ともに、まずはコールスタックに入る。
ただし、非同期処理のコールバックは、キューに入る。
コールスタックの処理が完了した後、キューに処理が残っている場合はそれをコールスタックに移し、処理が行われる。
そのため、非同期処理は同期処理より遅く処理される。= コードの上から順に処理されない。
Async
asyncが記述された関数は同期関数から非同期関数に変化し、常にPromiseを返す。
コード中にPromiseを返さないreturnがある場合、JavaScriptは自動的にその値を解決(resolve)されたPromiseにラップする。
つまり、次の全てのコードは同じ振る舞いをする。
async function func() {
return 1;
}
function func() {
return Promise.resolve(1);
}
function func() {
return new Promise((resolve, reject) => {
resolve(1);
});
}
func().then((num) => {
console.log(num); // 1~3ともに、結果は1
});
Await
asyncの中では、awaitを指定した行はPromiseが解決されるのを待つ。= 上から順に処理される。(asyncは必須)
つまり、非同期処理を同期処理っぽく動かせる。
Promise.all()
並行で(非同期で)処理するほうが効率がよい場面では、awaitで待たせてはいけない。
Promise.all()を使うことで、複数の非同期処理を並列で処理し、全て完了させた後にコールバックできる。(Promise.all()を待たせる(awaitする))
async function showNewData() {
const allData = await fetchAllData();
const oldData = await fetchOldData(); // allDataに待たされる
showData(allData, oldData);
}
async function showNewData() {
const [allData, oldData] = await Promise.all([fetchData(), fetchOldData()]);
showData(allData, oldData); // allData, oldDataともに完了すると実行される
}