101
89

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はなぜ生まれたのか ~ 非同期処理の歴史を辿る ~

101
Last updated at Posted at 2026-02-21

現在42Tokyoで最後のチーム課題(Webアプリ開発)に取り組んでいます。
JavaScript/TypeScriptで非同期処理を書くとき、なんとなくasync/awaitで書いてるけど
なぜこの書き方なんだろう?、とふと疑問に思ったので、その歴史を辿ってみました。
※JS視点で書いています。お手柔らかに。。。

非同期処理の進化史:コールバックからasync/await

JavaScriptにおいて、時間のかかる処理(API通信やタイマーなど)をどう扱うかは常に大きな課題でした。その進化の歴史を、コードの変遷とともに振り返ります。

歴史の流れ

世代 手法 進化のポイント
第1世代 コールバック 全てを手動で管理(ミスが起きやすい)
第2世代 Promise (ES6/2015) 非同期処理を「オブジェクト」として扱えるようになり、標準化された
第3世代 async/await (ES2017) Promiseをベースに、さらに「見た目」を同期処理に近づけて完成形へ

1. コールバック時代 (黎明期)

初期のJavaScriptでは、関数の中に「終わったら実行してほしい関数(コールバック)」を引数として渡すのが唯一の方法でした。

特徴

  • 関数を呼び出す際に、その後の予定も一緒に預ける。
  • 処理が連続すると、右にどんどんズレていく「コールバック地獄」が発生する。

エラーハンドリングの欠如

  • 重複 : failureCallback を何度も書かなければならず、コードが冗長になってしまう。
  • 見落とし : どこか一箇所でも failureCallback を渡し忘れると、そこでエラーが起きても何も起きない(握りつぶされる)危険がある。
  • 可読性 : どんどん右側にズレるため、どこがどの処理の終わりなのか分からなくなる。
// 右へ右へと深くなるネスト
getData(function(a) {
    getMoreData(a, function(b) {
        getEvenMoreData(b, function(c) {
            console.log(c);
        }, failureCallback);
    }, failureCallback);
}, failureCallback);

2. Promiseの登場 (2015年 / ES6)

コールバックの欠点を解決するために、「将来の結果を約束するオブジェクト」としてPromiseが登場しました。

特徴

  • 状態の管理 : Pending (待機) , Fulfilled (成功) , Rejected (失敗) の3状態を持つ。
  • メソッドチェーン : .then() で処理を縦につなげられる。
  • エラーの一括管理 : 最後に .catch() を書くだけで、道中で起きたエラーも捕まえられる。
// 縦に流れる「チェーン」構造
getData()
    .then(a => getMoreData(a))
    .then(b => getEvenMoreData(b))
    .then(c => console.log(c))
    .catch(error => console.error(error)); // ← これ一つで、全ステップの失敗をキャッチできる!
  • 一括管理 : 一度成功したら、後から失敗に変わることはないのでどこで失敗しても、最後の .catch が拾ってくれる。
  • 平坦 : .then() をつなげるだけで、非同期処理を1つのパイプラインのように連結できるようになった。コールバックのように階段状にならず、上から下へ流れるように読める。

3. async/awaitの登場 (ES2017)

  • async/awaitはJavaScriptが独自に生み出したものではなく、C# 5.0 (2012年) で先に導入された概念をモデルにしている。
  • Promiseをベースに、さらに「同期処理(上から下へ順番に動く普通のコード)」と同じ見た目で書けるようになった。
  • 「成功したら次へ、失敗したら catch へ」という流れが、普通の if 文やエラー処理と同じ感覚で書けるようになる。
比較項目 C# JavaScript
戻り値の型 Task または Task<T> Promise
キーワード async/await async/await
エラー処理 try-catch が使える try-catch が使える

どちらの言語も、コンパイラやエンジンが裏側で状態マシン(State Machine)を作成し、処理を一時中断・再開できるように制御している。これにより、スレッド(JSの場合はメインスレッド)をブロックせずに待機が可能になる。

特徴

  • async : 非同期関数であることを宣言する。
  • await : Promiseの結果が返るまでその行で待機する(その間、実行スレッドをブロックしない)。
  • 究極の読みやすさ : 非同期であることを意識せずにコードが書ける。
async function executeTasks() {
    try {
        const result = await doSomething();
        const newResult = await doSomethingElse(result);
        const finalResult = await doThirdThing(newResult);
        console.log(`最終結果: ${finalResult}`);
    } catch (error) {
        // どの await で失敗しても、ここに来る
        failureCallback(error);
    }
}

現代では、特別な理由がない限りコールバックを直接ガリガリ書くことは減り、「Promiseを返す関数を、async/awaitでスマートに呼び出す」のが標準スタイルになっている。

まとめ : なぜ進化したのか?

時代 手法 主な解決策
~2014年 コールバック 非同期処理の基本。ただしネストが深く制御が困難。
2015年~ Promise Promiseが明示的に解決。オブジェクトとして結果を扱えるように。
2017年~ async/await 「可読性」を解決。同期処理と同じ感覚で書けるように。

私も正直調べるまでは深く考えずに非同期処理はasync/awaitで書くかーと思っていましたが、調べてみるとコールバック地獄の理由やなぜasync/awaitが優れているかが理解できました!
また、Promiseオブジェクトについてももっと知りたくなったので、内容がまとまったらそっちの記事も書きたいと思います!
最後まで読んでいただきありがとうございます!💪

参考資料
MDN - Promiseの使用
MDN - async function

101
89
6

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
101
89

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?