async と await
このレッスンで分かること
awaitは中身が確定するまで、その関数だけを中断します- 前の結果を次に使いたいときは、
awaitを縦に並べます
続きの処理が、どんどん奥へ押し込まれていく
.then に関数を渡す書き方には、ひとつ不便なところがあります。受け取った値を使う処理が、全部その関数の中に入ってしまうことです。届いた値をもとに次を取り、さらにその結果を使う、と続けるたびに、コードは奥へ奥へ沈んでいきます。
await を使うと、この沈み込みが消えます。中身が確定するまでその関数だけを中断し、確定したら続きから再開する、という書き方だからです。止まるのは関数の続きだけで、ページ全体は動いたままです。
JavaScript
async function showBadge() {
const profile = await fetchProfile();
const badge = await fetchBadge(profile.level);
console.log(badge.label);
}1 行目でプロフィールを取り、2 行目でその中の値を使ってバッジを取っています。2 行目は 1 行目の結果が無いと始められないので、順番に待つのが正しい書き方です。上から下へ読める形になるところが、await のいちばんのありがたみです。
await は async を付けた関数の中でしか書けません。付け方は関数の書き方ごとに次のとおりです。
| 書き方 | 付ける場所 |
|---|---|
| 関数宣言 | async function showBadge() { } |
| 関数式 | const showBadge = async function () { }; |
| アロー関数 | const showBadge = async () => { }; |
| イベントに渡す関数 | button.addEventListener("click", async function () { }); |
縦に並べた await は、待ち時間が足し算になる
前の結果を次に使うなら縦に並べるのが正しいのですが、互いに関係の無い 2 つを縦に並べると、待ち時間をむだに足しています。
JavaScript
// 100 ミリ秒 + 100 ミリ秒 で 200 ミリ秒かかる
const profile = await fetchProfile();
const notice = await fetchNotice();この 2 つは互いの結果を使っていないので、本当は同時に始めてよいはずです。並べる前に「前の結果を使うのか」を一度確かめる癖を付けておくと、無駄な待ちを見つけやすくなります。
無い値を、次の await に渡さない
早期 return は非同期でもそのまま使えます。むしろ非同期のほうが重要で、無い値を次の await に渡すと、追いにくいエラーになります。
JavaScript
async function showBadge() {
const profile = await fetchProfile();
if (!profile.level) {
return "バッジはまだありません";
}
const badge = await fetchBadge(profile.level);
return badge.label;
}profile.level が無いまま fetchBadge(undefined) を呼ぶと、箱の中で落ちます。await を挟んだ先で落ちたエラーは、どこから来たのか分かりにくいので、渡す前に止めるのが安全です。
asyncを付けた関数からreturnした値は、そのまま返るのではなく Promise に包まれて返ります。上の関数は文字列を返していますが、呼ぶ側が受け取るのは「あとで文字列が入る箱」です。呼ぶ側でもawaitを書いてください。
よくある間違い
asyncを付け忘れてawaitを書く — 実行前に構文エラーになります。await is only valid in async functionsのような文が出たら、外側の関数にasyncが付いているかを見てくださいforEachの中でawaitを書く —forEachは、渡された関数が Promise を返しても待ちません。下のコードは中のawaitが終わる前にreturnへ進むので、空の配列が返ります。順番に待ちたいときはfor-ofを使ってください
JavaScript
async function collectLabels(profiles) {
const labels = [];
profiles.forEach(async function (profile) {
const badge = await fetchBadge(profile.level); // 外側は待ってくれない
labels.push(badge.label);
});
return labels; // まだ空のまま返る
}要件
- fetchWorkList(works) を作る。60 ミリ秒後に works をそのまま resolve する Promise を返す
- fetchViews(work) を作る。60 ミリ秒後に work.views を resolve する Promise を返す
- loadWorkSummary(works, id) を async function として作り、一覧を await してから id の作品を探す
- 見つかったら「タイトル / 表示回数 回表示」の文字列を返す。見つからなければ fetchViews を呼ばずに「作品が見つかりません」を返す
入出力例
loadWorkSummary([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}], 1) → "プロフィールサイト / 120 回表示"
loadWorkSummary([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}], 5) → "学習ログ / 0 回表示"
loadWorkSummary([{"id":1,"published":true,"title":"プロフィールサイト","views":120,"year":2026},{"id":2,"published":true,"title":"自己紹介カード","views":48,"year":2026},{"id":3,"published":true,"title":"スキル一覧の表","views":32,"year":2026},{"id":4,"published":true,"title":"作品ギャラリー","views":76,"year":2026},{"id":5,"published":false,"title":"学習ログ","views":0,"year":2025}], 99) → "作品が見つかりません"
loadWorkSummary([], 1) → "作品が見つかりません"