非同期JavaScript:API通信とモジュール
同期と非同期
このレッスンで分かること
- 時間のかかる処理は、待つのではなく予約して先へ進む形で書きます
- 予約した処理は、いま動いている流れが全部終わってから呼ばれます
3 秒のあいだ、ページを触れなくなる
JavaScript を動かす作業員は 1 人だけです。いま手に持っている仕事をやり切るまで、次の仕事には移れません。ボタンを押したときの処理も、画面を描き直す仕事も、同じ 1 人が順番にこなしています。この人が 1 か所で止まると、ページ全体が止まります。
JavaScript
function waitBadly(seconds) {
const end = Date.now() + seconds * 1000;
while (Date.now() < end) {
// ひたすら現在時刻を確かめ続けるだけ
}
}
console.log("重い処理を始めます");
waitBadly(3);
console.log("終わりました");この while が回っているあいだ、作業員はここに釘付けです。3 秒たつまで、文字を選ぶこともスクロールもできません。待つという行為をこう書いてはいけない、というのが非同期を学ぶ出発点です。
「止まる」と「待つ」は別のことです。止まるのは作業員がその場から動けない状態、待つのは別の仕事に移りながら、返事が来たら戻ってくる状態を指します。非同期処理が扱うのは後者だけです。
予約を置いて、先へ進む
非同期の書き方では、時間のかかる仕事を作業員が抱え込みません。終わったら呼んでくださいと紙に書いて渡し、自分はすぐ次の行へ進みます。入門で使った setTimeout がまさにこれでした。
JavaScript
console.log("1 番目");
setTimeout(function () {
console.log("3 番目に出る");
}, 1000);
console.log("2 番目");出力は 1 番目 2 番目 3 番目に出る の順です。setTimeout の行で 1 秒止まるのではなく、予約を置いてすぐ下の行へ進みます。予約した処理が呼ばれるのは、いま動いている流れがすべて終わってからです。書いた順と動く順が一致しないのが非同期のいちばん大きな特徴で、この性質があるおかげでページが固まりません。
| 同期 | 非同期 | |
|---|---|---|
| 書いた行で | 結果が出るまで止まる | 予約だけして次の行へ進む |
| 画面は | そのあいだ固まる | 動き続ける |
| 結果を受け取る場所 | 同じ行の戻り値 | あとから呼ばれる関数の中 |
| 代表例 | filter や sort | タイマー、通信、ファイルの読み込み |
配列のメソッドは何件あっても、呼んだその行で結果が返ります。一方、通信やファイルの読み込みは、終わるまでにどれくらいかかるか JavaScript 側からは分かりません。分からないから、待つ側が待ち方を選べる形にしてあります。
3 段つなぐと、右へ倒れていく
予約した処理を関数で渡す書き方をコールバックと呼びます。ところが、これだけで順番のある仕事をつなぐと、すぐに読めなくなります。コメントの一覧を取り、その 1 件目の本文を取り、さらに書いた人を取る、という 3 段です。
JavaScript
loadComments(function (comments) {
loadBody(comments[0].id, function (body) {
loadWriter(body.writerId, function (writer) {
console.log(writer.name);
});
});
});3 段でこの深さです。失敗したときの処理を足すと、階層ごとに引数が増えてさらに右へ伸びます。深さそのものより困るのは、失敗の扱いが階層ごとにばらばらになることです。外側の try は、予約した処理が呼ばれるころにはとっくに終わっているので、中で起きた失敗を受け取れません。
この問題を言語の側で片づけたのが Promise です。予約のたびに関数を渡すのではなく、あとで値になる箱を戻り値として受け取る形にそろえました。
JavaScript
const box = loadComments(); // 呼んだ瞬間に箱だけが返る戻り値なので、変数に入れられますし、関数から返せますし、配列に並べることもできます。コールバックを引数で渡していたときには、どれもできませんでした。
よくある間違い
- 非同期の結果を、同期のつもりで受け取る — 下のコードはいつでも
0が出ます。console.logは予約より先に動くからです。値を使う処理は、届いたあとの場所へ移します
JavaScript
let comments = [];
setTimeout(function () {
comments = [{ body: "はじめまして" }];
}, 100);
console.log(comments.length); // 0- 待つつもりで空回りのループを書く — 作業員がループに釘付けなので、100 ミリ秒後になっても予約は実行されません。ループを抜けてからようやく動きます