LASSIC Media らしくメディア
async/awaitの基本、非同期処理を順番に書く仕組み
監修・編集責任者:牛尾 昭昌(株式会社LASSIC 執行役員)
この記事の結論
- async/awaitは、時間のかかる処理の完了待ちを上から順に書ける構文で、awaitは関数を止めるだけでスレッドは止めません。
- 前後に依存がある処理だけを順番にawaitし、互いに独立した処理はPromise.all()やasyncio.gather()でまとめて待ちます。
- awaitの付け忘れ、同期の待ちの混入、失敗したときの扱いが典型的なつまずきで、設計とレビューの段階で先に確かめます。
※ 本記事は2026年10月時点の公式情報(省庁・公的機関、および製品やサービスを提供する事業者が公開している資料)に基づきます。
async/awaitは、時間のかかる処理の完了を待つコードを、上から順に読める形で書くための構文です。APIの呼び出しやデータベースへの問い合わせを待つ処理を、コールバックを何重にも重ねずに書けるため、JavaScript(TypeScript)・Python・C#など多くの言語で使われています。一方で、awaitの付け忘れや、待たなくてよい処理まで一つずつ待つ書き方は、不具合の原因になりがちです。
本記事では、システム開発やIT運用に携わる方に向けて、async/awaitの仕組みを言語仕様と公式ドキュメントに沿って整理し、短いコード例、使いどころ、つまずきやすい点、外部に頼むときの確認点まで取り上げます。
目次
async/awaitとは
asyncは関数を「非同期関数」として宣言するキーワードで、awaitはその中で処理の完了を待つための演算子です。JavaScriptの言語仕様であるECMAScriptは、async関数をAsyncFunctionという種類の関数オブジェクトとして定めています。MDNは、async関数を呼び出すたびに新しいPromiseが返り、そのPromiseは関数の戻り値で解決されるか、関数の中で捕まえられなかった例外で拒否される、と説明しています。*2
Promiseは、まだ終わっていない処理の結果を表すオブジェクトです。awaitを付けると、そのPromiseが決着する(成功か失敗かが確定する)まで関数の実行が止まり、成功した値がawait式の値になります。失敗した場合は、awaitの位置で例外が投げられます。そのため、非同期処理のエラーもふつうのtry/catchで扱えます。
MDNは、async/awaitの目的を、Promiseを使うAPIを利用するための構文を簡単にすることだと位置づけています。新しい並行処理の仕組みではなく、Promiseを書きやすくする書き方だと捉えると理解しやすくなります。同期処理と非同期処理の違いそのものは「同期処理と非同期処理の違い」で整理しています。
awaitで止まって再開する仕組み
async関数の中身は、最初のawaitまでは呼び出した時点でそのまま実行されます。awaitに達すると関数はそこで一時停止し、呼び出し元にPromiseを返して制御を戻します。待っていたPromiseが決着すると、awaitの次の行から実行が再開されます。MDNは、awaitの後ろのコードは.then()のコールバックの中にあるものと考えられる、と説明しています。*2
言語仕様では、この動きがAwaitという抽象操作として書かれています。受け取った値をPromiseに変換し(PromiseResolve)、成功したときと失敗したときに再開する処理を登録したうえで、実行中の文脈を中断して呼び出し元へ戻る、という手順です。*1
待っているあいだ、スレッドは止まっていません。MDNは、await式がメインスレッドをブロックすることはなく、結果に依存するコード、つまりawaitより後ろの部分の実行を後回しにするだけだと述べています。*3 後回しにされた処理を順に取り出して実行するのがイベントループで、その仕組みは「イベントループの仕組み」で説明しています。
コールバック・Promiseとの違い
以前は、処理が終わったときに呼ぶ関数を渡すコールバックや、Promiseの.then()をつなぐ書き方が中心でした。3つを並べると次のとおりです。
| 観点 | コールバック | Promiseの.then() | async/await |
|---|---|---|---|
| 順番に処理する書き方 | 関数の中に関数を入れ子にする | .then()を鎖のようにつなぐ | 上から順に文を並べる |
| エラーの受け方 | コールバックごとに引数で確かめる | .catch()で受ける | try/catchで受ける |
| 途中の値の受け渡し | 入れ子の外側の変数に置く | 次の.then()へ戻り値で渡す | ふつうの変数に代入する |
| 土台の仕組み | 関数を渡すだけ | Promise | Promise(.then()と同じ仕組み) |
async/awaitはPromiseの上に作られているため、両者は混ぜて使えます。async関数の戻り値に.then()をつなぐことも、Promiseを返す既存の関数をawaitで待つこともできます。既存のコードは、手を入れる箇所から順に書き換えていけます。
具体例:順番に待つか、まとめて待つか
async/awaitで最初に押さえたいのは、awaitを並べると処理が1つずつ順番に進むことです。Pythonの標準ライブラリasyncioで確かめます。在庫と価格を取る2つの処理を、待ち時間1秒と2秒のasyncio.sleep()で代用しています。
import asyncio, time
async def fetch(name, sec):
await asyncio.sleep(sec) # API呼び出しの待ち時間の代わり
return f"{name}({sec}秒)"
async def main():
t = time.perf_counter()
a = await fetch("在庫", 1) # 1つ目が終わってから
b = await fetch("価格", 2) # 2つ目を始める
print("順番に待つ:", a, b, f"{time.perf_counter() - t:.1f}秒")
t = time.perf_counter()
a, b = await asyncio.gather(fetch("在庫", 1), fetch("価格", 2))
print("まとめて待つ:", a, b, f"{time.perf_counter() - t:.1f}秒")
asyncio.run(main())
手元のPython 3.12で実行すると、「順番に待つ」は3.0秒、「まとめて待つ」は2.0秒と表示されました。前半は1つ目のawaitが終わるまで2つ目が始まらないため、待ち時間が足し算になります。後半のasyncio.gather()は、渡した処理を並行に走らせ、すべて終わると結果を渡した順のリストで返します。*5 待ち時間は長いほうの2秒で済みます。
JavaScriptではPromise.all()が同じ役割を持ちます。次の例は、注文を取ってからその品目の在庫を取る処理を、2件の注文について同時に進めます。注文の中身が分からないと在庫を引けないので、1件の中は順番に待ちます。
const wait = (ms, v) => new Promise((resolve) => setTimeout(() => resolve(v), ms));
async function loadOrder(id) {
try {
const order = await wait(1000, { id, item: "A-100" }); // 注文を取る
const stock = await wait(1000, 12); // その品目の在庫を取る
return { ...order, stock };
} catch (e) {
console.error("読み込みに失敗:", e.message); // 拒否はここで受け取れる
throw e;
}
}
const t = Date.now();
const results = await Promise.all([loadOrder(1), loadOrder(2)]);
console.log(results.length, "件", Math.round((Date.now() - t) / 1000), "秒");
Node.js 24で実行すると「2 件 2 秒」と表示されました。1件の中は1秒ずつ順番に待ち、2件どうしは並行に進むためです。前後に依存がある処理は順番に、独立した処理はまとめて待つ。この使い分けが設計の中心です。
PythonとC#での書き方
Pythonでasync defとawaitの構文を定めたのはPEP 492で、Python 3.5で入りました。PEP 492は、コルーチンをPythonの独立した概念にし、非同期のプログラムをできるだけ同期のプログラムに近い形で書けるようにすることを目標に掲げています。*7
JavaScriptとの大きな違いは、呼んだだけでは動かない点です。公式ドキュメントは、コルーチンを単に呼び出しても実行は予約されないと注意しています。*5 動かすには、最上位でasyncio.run()に渡すか、別のコルーチンの中でawaitするか、asyncio.create_task()でタスクにします。JavaScriptと同じ感覚で書くと、処理が一度も動かないことがあります。
C#では、TaskまたはTask<T>を返すメソッドにasyncを付け、中でawaitを使います。Microsoft Learnはこの書き方をタスク非同期プログラミング(TAP)モデルと呼び、文を並べた順に読めるが、実際にはもっと複雑な順序で実行されるコードを書けるようにすることが目標だと説明しています。*8 同じ記事は朝食づくりを例に、非同期の処理は並列でなくても進められると説明しています。
実務での使いどころ
async/awaitが役立つのは、処理時間の大半が外部の応答待ちで占められる場面です。Microsoft Learnは、ネットワーク経由のデータ要求、データベースへのアクセス、ファイルの読み書きといったI/Oが中心の処理には、非同期プログラミングが最適だとしています。*9 具体的には次のような場面です。
- WebサーバーやAPIサーバーで、1つのリクエストが別のAPIやデータベースの応答を待つあいだに、ほかのリクエストを受け付ける
- 画面を持つ業務アプリで、通信やファイル読み込みのあいだも操作を受け付け、画面を固めない
- バッチ処理で、多数のファイルやAPIを相手にする待ち時間を重ねて短くする
反対に、CPUを使い続ける計算は、async/awaitだけでは速くなりません。Pythonの公式ドキュメントは、CPUを使い続けるコードを直接呼ぶべきではないとし、1秒かかる計算をすると、並行に動いているすべてのタスクとI/Oが1秒遅れると例を挙げています。*6 C#では、Task.Runで別のスレッドに計算を任せ、その完了をawaitで待つ書き方が案内されています。スレッドとプロセスの使い分けは「プロセスとスレッドの違い」を参照してください。
つまずきやすい点
最も多いのは、awaitの付け忘れです。Pythonでは、一度も待たれなかったコルーチンについて「coroutine … was never awaited」というRuntimeWarningが出ます。*6 JavaScriptでは警告が出ないまま、値の代わりにPromiseが変数に入り、後続の処理が思わぬ値で動きます。リンターで、待たれていないPromiseを検出する規則を有効にしておきます。
2つ目は、エラーの受け止め漏れです。MDNは、2つのPromiseを作ってから順に1つずつawaitする書き方に「Do not do this!」と注記しています。1つ目を待つあいだに2つ目が失敗すると、呼び出し元にcatchがあっても、処理されない拒否(unhandled rejection)になるためです。*2 並行に進めた処理は、Promise.all()やPromise.allSettled()でまとめて待ちます。ただしPromise.all()は、1つが失敗して拒否されても、残りの処理を取り消すわけではありません。*4
3つ目は、async関数の中で同期の待ちを使ってしまうことです。Pythonでtime.sleep()や同期のHTTPクライアントを呼ぶと、その間はイベントループ全体が止まります。避けられない同期処理は、asyncio.to_thread()で別のスレッドに移せます。
C#では、Task.WaitやTask.Resultで結果を同期的に待つと、デッドロックやスレッドの停滞を招くおそれがあるとMicrosoft Learnは注意しています。async voidはイベントハンドラーだけに使い、それ以外はTaskを返すよう勧められています。async voidのメソッドの例外は外で捕まえられず、テストもしにくいためです。*9
外部に委託するときに確認しておきたい点
外部に頼むときは、まず非同期にする範囲を確かめます。外部の応答を待つI/O中心の処理と、CPUを使い続ける処理を、設計の段階で分けてもらいます。
次に、並行に進める処理の上限です。まとめて待つと、外部APIやデータベースへ一度に多くの要求が飛びます。相手の流量制限や接続数の上限を踏まえ、同時実行数の絞り方を提案に含めてもらいます。
三つ目は、失敗とタイムアウトの扱いです。まとめて待つ処理の1つが失敗したとき、残りを取り消すのか、成功した分だけ使うのか、どこまで待って打ち切るのかを、業務の要件として決めておきます。Pythonのasyncio.gather()は、既定では最初の例外を伝えた後も残りの処理を走らせ続けます。一方、asyncio.TaskGroupは、1つのタスクが失敗した時点で残りのタスクを取り消します。*5
最後に、レビューとテストの手当てです。awaitの付け忘れを見つける静的解析、非同期処理の単体テスト、本番の応答時間の監視を、納品や運用の範囲に含めてもらえるかを見ます。
まとめ:async/awaitで確かめたい3つの点
確かめておきたい点は3つです。第一に、async関数はPromise(Pythonではコルーチン)を返し、awaitは関数を止めるだけでスレッドは止めないという仕組みを押さえること。第二に、前後に依存がある処理だけを順番にawaitし、独立した処理はPromise.all()やasyncio.gather()でまとめて待つこと。第三に、awaitの付け忘れ、同期の待ちの混入、失敗したときの扱いを、設計とレビューの段階で確かめることです。設計やレビューに迷いがあれば、外部の手を借りるのも一つの選択肢です。
よくある質問
async/awaitを使うと、処理は並列に動きますか
async/await自体は、処理を並列に動かす仕組みではありません。待ち時間のあいだに別の処理を進める並行の仕組みで、Pythonのasyncioでは、イベントループは1つのスレッド(多くはメインスレッド)の上で動きます。CPUを使う計算を同時に進めたい場合は、スレッドやプロセスを別に使います。
awaitは、async関数の外でも使えますか
JavaScriptでは、awaitは原則としてasync関数の中でしか使えず、外で使うとSyntaxErrorになります。例外はJavaScriptのモジュールで、モジュールの最上位ではawaitを単独で使えます。Pythonでは、最上位からasyncio.run()で起動するのが基本です。
コールバック形式の古いコードも、awaitで待てますか
Promiseを返す形に包み直せば、awaitで待てます。Promiseのコンストラクター(new Promise)の中で元の関数を呼び、完了時のコールバックでresolveを、失敗時にrejectを呼ぶのが基本の形です。本記事のJavaScriptの例のwait関数がその形です。
async/awaitを使った非同期処理のご相談
元請(プライムベンダー)として、非同期処理の設計から実装、既存コードの見直し、保守・運用までご提案します。
Remoguとリラシクなら、Node.jsやPython、C#での非同期処理の開発に加わるITエンジニアも探せます。
Remoguは、リモート前提で全国から即戦力のITプロ人材を調達するサービスです。リラシクは、扱う求人がすべてリモートワークのITエンジニア専門転職エージェントです。どちらもLASSICが運営しています。
出典
- *1 参考:Ecma International「ECMAScript® 2025 Language Specification」(ECMA-262 第16版)(https://tc39.es/ecma262/2025/)。出典:第15.8節(Async Function Definitions)、第27.7節(AsyncFunction Objects)、第27.7.5.3節(Await)を参照(2026年10月確認)
- *2 参考:MDN Web Docs「async function」(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)。出典:Description(呼び出すたびに新しいPromiseを返す、awaitの後ろは.then()のコールバックと考えられる、順に待つ書き方の注意)と、await and concurrency の節を参照(2026年10月確認)
- *3 参考:MDN Web Docs「await」(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)。出典:Description(拒否されたPromiseは例外として投げられる、Promise以外の値の扱い、メインスレッドをブロックしない)を参照(2026年10月確認)
- *4 参考:MDN Web Docs「Promise.all()」(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)。出典:Description(1つが拒否された時点で拒否される、残りの処理は取り消されない)を参照(2026年10月確認)
- *5 参考:Python Software Foundation「Coroutines and Tasks」(Python 3 ドキュメント)(https://docs.python.org/3/library/asyncio-task.html)。出典:Coroutines(呼ぶだけでは実行が予約されない)、create_task()、TaskGroup、gather()、to_thread() の説明を参照(2026年10月確認)
- *6 参考:Python Software Foundation「Developing with asyncio」(Python 3 ドキュメント)(https://docs.python.org/3/library/asyncio-dev.html)。出典:Debug Mode(100ミリ秒を超えるコールバックの記録)、Concurrency and Multithreading、Running Blocking Code、Detect never-awaited coroutines を参照(2026年10月確認)
- *7 参考:PEP 492 – Coroutines with async and await syntax(https://peps.python.org/pep-0492/)。出典:ヘッダー(Python-Version 3.5)と Abstract(コルーチンを独立した概念にし、同期のプログラムに近い形で書けるようにする)を参照(2026年10月確認)
- *8 参考:Microsoft Learn「Asynchronous programming with async and await」(C#)(https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/)。出典:タスク非同期プログラミング(TAP)モデルの説明と、朝食づくりの例(非同期だが並列ではない作業)を参照(2026年10月確認)
- *9 参考:Microsoft Learn「Asynchronous programming scenarios」(C#)(https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios)。出典:I/O中心とCPU中心の処理の見分け方、Task.Run、async void をイベントハンドラーに限る理由、Task.Wait・Task.Result によるデッドロックの注意を参照(2026年10月確認)