0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「async/awaitってなんやねん」を完全に理解した気になる記事

Posted at

はじめに

現代のJavaScriptには、 async / await という、非同期処理をまるで同期処理(上から順番に実行される普通の処理)のように書ける、魔法のような機能があります。

この記事では、async/await が「なんやねん」状態の人が、「なるほど、こういうことか!」と完全に理解した気になれるよう、わかりやすく解説していきます。

そもそも「非同期処理」って?

async/await を知る前に、なぜそれが必要なのか、つまり「非同期処理」が何かを知る必要があります。

  • 同期処理
    • 一つのタスクが終わるまで、次のタスクは待機します。
    • 例:レジに並ぶお客さん。前の人が終わらないと自分の番は来ません。

  • 非同期処理
    • 時間のかかるタスク(例:サーバーからデータを取ってくる、ファイルを読み込む)を待たずに、次のタスクに進みます。タスクが終わったら、後で結果を受け取ります。
    • 例:レストランの注文。注文(タスク)を伝えたら、あなたは席で待ち(他のことができる)、料理ができたら(タスク完了)店員さんが持ってきてくれます。

JavaScriptは基本的にシングルスレッド(一度に一つのことしかできない)ですが、この非同期処理のおかげで、重い処理を待っている間もブラウザが固まったりせず、スムーズに動作できるのです!

Promise(約束)という前提

async/await は、実は Promise(プロミス) という仕組みを、もっと読みやすく、書きやすくするための構文です。

Promiseは「非同期処理の最終的な結果を表すオブジェクト」です。
平たく言えば、「今はまだ結果が出てないけど、いつか必ず結果(成功か失敗か)を返すという『約束』」です。

従来のPromiseの書き方を見てみましょう。

// データを取ってくる非同期関数(Promiseを返す)
function fetchData() {
  return new Promise((resolve, reject) => {
    // 2秒後にデータを返すシミュレーション
    setTimeout(() => {
      resolve("データ取得成功!");
      // もし失敗したら reject("エラー発生!");
    }, 2000);
  });
}

// Promiseを使った処理
fetchData()
  .then(data => {
    console.log(data); // 2秒後に "データ取得成功!"
    // さらに別の非同期処理...
    return anotherFetch(data);
  })
  .then(nextData => {
    console.log(nextData);
  })
  .catch(error => {
    console.error("エラー", error);
  });

このように .then() を使って、処理が成功した「後」の動作をチェーン(鎖)のようにつなげていきます。

便利ですが、処理が複雑になると、このチェーンが長くなり、読みにくくなることがありました。

「async/await」の使い方

お待たせしました。本題の async/await です。
これらはセットで使います。

1. async (非同期関数ですよ宣言)

関数を定義するとき、function の前に async を付けます。

async function myAsyncFunction() {
  // ...
}

// アロー関数なら
const myAsyncFunction = async () => {
  // ...
};

async を付けた関数は、必ず Promiseを返す 関数になります。
(もし関数内で何らかのreturn (例:return 123)がされても、自動的に Promise.resolve(123) のようにPromiseで包まれて返されます)

2. await (待っててね命令)

async を付けた関数の中でだけ使える特別なキーワードです。
await は、Promiseの結果が出るまで、その場(関数内)の処理を一時停止させます。

さっきのPromiseの例を async/await で書き換えてみましょう。

// データを取ってくる非同期関数(中身は同じ)
function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("データ取得成功!");
    }, 2000);
  });
}

// async/awaitを使った処理
async function processData() {
  console.log("処理開始");
  
  // fetchData() が終わる(Promiseが解決される)までここで待つ
  // 結果が data に格納される
  const data = await fetchData(); 
  
  console.log(data); // 2秒後に "データ取得成功!"
  console.log("処理終了");
}

processData();

/*
実行結果↓
処理開始
↓
(2秒待機)
↓
データ取得成功!
↓
処理終了
*/

どうでしょうか?
.then() のチェーンがなくなり、まるで上から順番に実行される同期処理のように読めませんか?

const data = await fetchData(); の部分がキモです。
Promiseの結果(resolveされた値)を、await が「待って」くれて、変数 data に直接代入してくれているのです。

エラーハンドリング (try...catch)

.then() がないなら、エラーを捕まえる .catch() はどうなるの?」

良い質問です。async/await では、同期処理で使うおなじみの try...catch 構文を使います。

async function processDataWithError() {
  try {
    console.log("処理開始");

    // わざと失敗するPromise
    const data = await new Promise((resolve, reject) => {
      setTimeout(() => reject("ネットワークエラー!"), 2000);
    });

    console.log(data); // ここは実行されない

  } catch (error) {
    // await したPromiseが reject されると、ここでキャッチできる
    console.error("エラーが発生しました", error); // 2秒後に "エラーが発生しました ネットワークエラー!"
  
  } finally {
    console.log("処理完了(成功しても失敗しても実行)");
  }
}

processDataWithError();

これも、普段のJavaScriptのエラー処理と同じ書き方なので、非常に直感的ですね。

押さえておくべき3つの注意点

async/await は強力ですが、いくつか知っておくべきルールがあります。

1. awaitasync 関数の中でしか使えない

async が付いていない普通の関数や、グローバルスコープ(関数の外)で await を使おうとすると、エラーになります。

// ダメな例
function normalFunction() {
  const data = await fetchData(); // SyntaxErrorになる
}

補足:トップレベル await
2025年現在、ESM(ECMAScript Modules)と呼ばれるモジュールシステムの中では、async で囲まなくても await を使える「トップレベルawait」がサポートされています。
しかし、基本的なルールとして「awaitasync の中」と覚えておけば間違いありません。

2. await の使いすぎは「遅く」なる

async/await は、処理を「待たせる」機能です。
もし、互いに関係のない複数の非同期処理を、すべて await で待ってしまうと、無駄な待ち時間が発生します。

// 遅い例(逐次処理)
async function fetchAllSequentially() {
  console.time("sequential");

  const data1 = await fetchData(1); // 2秒待つ
  const data2 = await fetchData(2); // さらに2秒待つ
  const data3 = await fetchData(3); // さらに2秒待つ

  console.timeEnd("sequential"); // 約6秒かかる
}

data1data2data3 に関連がないなら、同時にリクエストを開始したいですよね。
そういう時は、Promise.all を使います。

// 速い例(並列処理)
async function fetchAllParallel() {
  console.time("parallel");

  // 3つの非同期処理を「同時に」開始する
  const promise1 = fetchData(1);
  const promise2 = fetchData(2);
  const promise3 = fetchData(3);

  // 3つすべてが終わるのを「待つ」
  const [data1, data2, data3] = await Promise.all([promise1, promise2, promise3]);

  console.timeEnd("parallel"); // 約2秒で終わる
}

async/await を使いつつも、処理の依存関係を意識し、並列化できるところは Promise.all を使うのがパフォーマンス向上のコツです。

3. async 関数は「常に」Promiseを返す

async 関数を呼び出したときの戻り値は、常にPromiseです。

async function getNumber() {
  return 10;
}

const result = getNumber();
console.log(result); // 10 ではなく、 Promise { <fulfilled>: 10 } と出力される

もし async 関数の結果を別の場所で使いたい場合は、呼び出し側も await するか、.then() を使う必要があります。

// 呼び出し側も async/await
async function useNumber() {
  const num = await getNumber();
  console.log(num); // 10
}

// .then を使う
getNumber().then(num => {
  console.log(num); // 10
});

まとめ

async/await を使えば、JavaScriptの非同期処理が、まるで上から下に流れる同期処理のように、直感的に読み書きできます。

  • 関数に async を付けて「非同期関数」を宣言する。
  • async 関数の中で、Promiseの結果を await で「待つ」。
  • エラー処理は try...catch で囲む。

もう .then() の連鎖やコールバック地獄に悩まされる必要はありません。

まずは簡単な処理から、async/awaitの使い方について徐々に慣れていきましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?