Part 0 · React の前に
7 / 64 章
待つ処理(Promise と async/await)
API 通信を読むための土台。順番が入れ替わる理由
ここまでのコードは、書いた順に、上から下へ実行されていました。
ですがサーバーへの問い合わせは違います。返事が来るまで時間がかかります。 その間、画面が固まってしまっては困ります。
そこで JavaScript には「あとで結果が来るもの」を扱う仕組みがあります。 この章はその話です。
順番が入れ替わるところを見る
「お茶を注文する」処理を書いてみます。 お茶が届くまでに 1 秒かかるとします。
両方押して、出てくる順番を見比べる
- 押すと、順番が出ます
「await なし」を押すと、2 番が最後に来ます。1 → 3 → 2 の順です。
add("1. お茶を注文した");
wait(1000).then(() => add("2. お茶が届いた"));
add("3. 席に座った");wait(1000) はその場で止まりません。 「1 秒たったらこれをやって」と予約だけして、先に進みます。 だから 3 番が先に実行されます。
order.ts
// 1 秒かかる処理のつもり。この 1 行は書けなくて構わない
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// ✕ 待たない書き方
export const withoutAwait = (add: (line: string) => void) => {
add("1. お茶を注文した");
wait(1000).then(() => add("2. お茶が届いた"));
add("3. 席に座った");
};
// ◯ 待つ書き方。await のところで、いったん止まる
export const withAwait = async (add: (line: string) => void) => {
add("1. お茶を注文した");
await wait(1000);
add("2. お茶が届いた");
add("3. 席に座った");
};Promise は「引換券」
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));wait(1000) が返すのは、お茶ではありません。「1 秒後にお渡しします」という引換券です。 この引換券を Promise と呼びます。
引換券には 3 つの状態があります。
- 待っている … まだ結果が出ていない
- 成功した … 値が入った
- 失敗した … エラーになった
(コードやエラーの表示では、それぞれpending / fulfilled / rejected と出てきます)
そして受け取り方が 2 通りあります。
// 昔ながらの書き方
wait(1000).then(() => add("届いた"));
// いまの書き方
await wait(1000);
add("届いた");やっていることは同じです。 違うのは「続き」の書き方だけ。.then は続きを関数にして渡す書き方、await は続きをそのまま下に書く書き方です。
下のほうが、上から下に読めるぶん分かりやすい。 だから await が使えるところでは、そちらを使います。
order.ts
// 1 秒かかる処理のつもり。この 1 行は書けなくて構わない
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// ✕ 待たない書き方
export const withoutAwait = (add: (line: string) => void) => {
add("1. お茶を注文した");
wait(1000).then(() => add("2. お茶が届いた"));
add("3. 席に座った");
};
// ◯ 待つ書き方。await のところで、いったん止まる
export const withAwait = async (add: (line: string) => void) => {
add("1. お茶を注文した");
await wait(1000);
add("2. お茶が届いた");
add("3. 席に座った");
};await は「ここで待つ」
const withAwait = async () => {
add("1. お茶を注文した");
await wait(1000); // ← ここで待つ
add("2. お茶が届いた");
add("3. 席に座った");
};こう書くと、1 → 2 → 3 の順になります。 デモの「await あり」を押すと確かめられます。
order.ts
// 1 秒かかる処理のつもり。この 1 行は書けなくて構わない
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// ✕ 待たない書き方
export const withoutAwait = (add: (line: string) => void) => {
add("1. お茶を注文した");
wait(1000).then(() => add("2. お茶が届いた"));
add("3. 席に座った");
};
// ◯ 待つ書き方。await のところで、いったん止まる
export const withAwait = async (add: (line: string) => void) => {
add("1. お茶を注文した");
await wait(1000);
add("2. お茶が届いた");
add("3. 席に座った");
};サーバーからデータを取ってくる
ここまでが分かると、API との通信が読めるようになります。
両方のボタンを押してみる
まだ取りに行っていません
fetch は、サーバーに問い合わせるための命令です。待つものが 2 回出てきます。
const response = await fetch(path); // 1 回目: 返事を待つ
const data = await response.json(); // 2 回目: 本文を読み終わるのを待つ1 回目で返ってくるのは「返事が届いた」という事実だけで、 中身はまだ読めていません。 本文を JavaScript の値に変換するのに、もう一度待ちます。
fetch-members.ts
// async を付けると、その中で await が使えるようになる
export const loadMembers = async (path: string) => {
try {
// 1. サーバーに問い合わせて、返事が来るまで待つ
const response = await fetch(path);
// 2. 404 や 500 でも fetch は成功扱い。自分で確かめる
if (!response.ok) {
throw new Error(`サーバーが ${response.status} を返しました`);
}
// 3. 本文を JavaScript の値に変換する。これも待つ
const data = await response.json();
return `取得できました: ${data.members.join("、")}`;
} catch (error) {
// 通信が切れた場合も、上で投げた場合も、ここに来る
return error instanceof Error ? `失敗: ${error.message}` : "失敗しました";
}
};失敗したときの書き方
通信は失敗するのが普通です。 電波が切れる、サーバーが落ちている、URL が間違っている。
try {
// うまくいく前提で書く
} catch (error) {
// 失敗したらここに来る
}try の中で失敗が起きると、そこから先を飛ばして catch に移ります。 成功時のコードに if を挟まなくて済むので、読みやすくなります。
fetch-members.ts
// async を付けると、その中で await が使えるようになる
export const loadMembers = async (path: string) => {
try {
// 1. サーバーに問い合わせて、返事が来るまで待つ
const response = await fetch(path);
// 2. 404 や 500 でも fetch は成功扱い。自分で確かめる
if (!response.ok) {
throw new Error(`サーバーが ${response.status} を返しました`);
}
// 3. 本文を JavaScript の値に変換する。これも待つ
const data = await response.json();
return `取得できました: ${data.members.join("、")}`;
} catch (error) {
// 通信が切れた場合も、上で投げた場合も、ここに来る
return error instanceof Error ? `失敗: ${error.message}` : "失敗しました";
}
};理解できたか確かめる
確認クイズ
await を付けないと、なぜ順番が入れ替わる?
確認クイズ
await を使う関数に必要なことは?
確認クイズ
サーバーが 404 を返したとき、fetch はどうなる?
order.ts
// 1 秒かかる処理のつもり。この 1 行は書けなくて構わない
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// ✕ 待たない書き方
export const withoutAwait = (add: (line: string) => void) => {
add("1. お茶を注文した");
wait(1000).then(() => add("2. お茶が届いた"));
add("3. 席に座った");
};
// ◯ 待つ書き方。await のところで、いったん止まる
export const withAwait = async (add: (line: string) => void) => {
add("1. お茶を注文した");
await wait(1000);
add("2. お茶が届いた");
add("3. 席に座った");
};この章のまとめ
- 時間のかかる処理はその場で止まらない。 止まったらブラウザ全体が固まるから
- Promise は引換券。 待っている / 成功した / 失敗した の 3 状態
awaitで上から下に読める形になる。 使う関数にはasyncが要るfetchは待つところが 2 回(返事、本文)- 404 や 500 は成功扱い。
response.okを自分で確かめる
fetch-members.ts
// async を付けると、その中で await が使えるようになる
export const loadMembers = async (path: string) => {
try {
// 1. サーバーに問い合わせて、返事が来るまで待つ
const response = await fetch(path);
// 2. 404 や 500 でも fetch は成功扱い。自分で確かめる
if (!response.ok) {
throw new Error(`サーバーが ${response.status} を返しました`);
}
// 3. 本文を JavaScript の値に変換する。これも待つ
const data = await response.json();
return `取得できました: ${data.members.join("、")}`;
} catch (error) {
// 通信が切れた場合も、上で投げた場合も、ここに来る
return error instanceof Error ? `失敗: ${error.message}` : "失敗しました";
}
};