本文へスキップ
待つ処理(Promise と async/await)

Part 0 · React の前に

7 / 64

待つ処理(Promise と async/await)

API 通信を読むための土台。順番が入れ替わる理由

ここまでのコードは、書いた順に、上から下へ実行されていました。

ですがサーバーへの問い合わせは違います。返事が来るまで時間がかかります。 その間、画面が固まってしまっては困ります。

そこで JavaScript には「あとで結果が来るもの」を扱う仕組みがあります。 この章はその話です。

順番が入れ替わるところを見る

「お茶を注文する」処理を書いてみます。 お茶が届くまでに 1 秒かかるとします。

await があるときと、ないとき

両方押して、出てくる順番を見比べる

  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}` : "失敗しました";
  }
};