本記事のコード例はすべて Node.js v26.5.0 で実際に実行し、出力をそのまま掲載しています。「〜が出力される」というコメントだけで済ませている箇所はありません。
JavaScriptで非同期処理が重要な理由
シングルスレッド
JavaScriptは基本的にシングルスレッドで動作します。これは、一度に1つのタスクしか実行できないことを意味します。Webブラウザ環境では、JavaScriptの実行スレッドは、ページのレイアウト、再描画(リフロー)、ガベージコレクションといったブラウザのUIレンダリング処理と同じスレッドを共有しています。
そのため、時間のかかるJavaScript処理がスレッドを占有してしまうと、ページの応答性が悪くなり、ユーザーインターフェースがフリーズしたように見えてしまう問題が発生します。この問題を解決するために、非同期処理が不可欠となります。
参考
非同期処理の仕組み
非同期処理は、時間のかかるタスク(例: ネットワークリクエスト、タイマー)をメインスレッドから切り離して実行し、そのタスクが完了するのを待たずに次の処理へ進むことを可能にします。これにより、UIのフリーズを防ぎ、複数の処理を並行して実行しているかのように見せることができます。
イベントループ (Event Loop) の概要
JavaScriptの非同期処理は、イベントループという仕組みによって実現されています。JavaScriptエンジン(V8など)は、以下の主要なコンポーネントと連携して動作します。
- JavaScriptエンジン:
- ヒープ (Heap): オブジェクトや変数が格納されるメモリ領域です。
- コールスタック (Call Stack): 実行中の関数呼び出しをLIFO(後入れ先出し)形式で管理する領域です。関数が呼び出されるとスタックに積まれ、実行が完了するとスタックから取り除かれます。
- Web APIs(ブラウザ環境)/ libuv(Node.js環境):
- ブラウザが提供するAPI群(DOM操作、Ajaxリクエスト、
setTimeoutなどのタイマー機能など)です。これらはJavaScriptエンジンとは別のスレッドで動作します。Node.jsでは同様の役割をlibuvというC言語ライブラリが担い、タイマーやファイルI/O、ネットワークI/Oをスレッドプールやカーネルの非同期機構と連携して処理します。
- ブラウザが提供するAPI群(DOM操作、Ajaxリクエスト、
- タスクキュー(後述するマクロタスクキューとマイクロタスクキュー):
- Web APIs/libuvから受け取ったコールバック関数が格納されるキューです。実はこのキューは1種類ではなく、優先順位の異なる2種類が存在します。これは非常に重要な点なので、後の章「 イベントループを正確に理解する 」で詳しく解説します。
イベントループは、大まかに次の流れで非同期処理を調整します。
- JavaScriptコードが実行され、関数呼び出しがコールスタックに積まれます。
setTimeoutやfetchなどの非同期関数が呼び出されると、そのタスクはWeb APIs/libuvに渡され、そちらの実行環境で処理が開始されます。- 処理が完了すると、その結果(コールバック関数)がタスクキューに格納されます。
- イベントループは、コールスタックが空になる(メインスレッドで実行中のタスクがなくなる)のを常に監視しています。
- コールスタックが空になると、イベントループはタスクキューからコールバック関数を取り出し、コールスタックに積んで実行します。
この「大まかな流れ」だけでは説明できない、実務でよく驚かれる挙動(setTimeout(fn, 0) が「即座に」実行されない理由など)があります。それを理解するには、タスクキューが実は2種類あり、両者の間に厳格な優先順位があることを知る必要があります。詳細は後述します。
参考
- 非同期処理 (1):Javascriptの動作の流れ (JS エンジン/Call Stack/Event Queue) | Zenn
- JavaScriptのイベントループを理解する | Qiita
- JavaScriptがブラウザでどのように動くのか | mercari engineering
- The Node.js Event Loop | Node.js公式ドキュメント - Node.js環境における libuv のフェーズ(timers/pending callbacks/poll/check/close callbacksなど)を解説した公式ガイドです。
コールバック地獄 (Callback Hell)
非同期処理の初期のJavaScriptでは、処理の完了を待って次の処理を実行するために、コールバック関数が多用されました。しかし、非同期処理が複数連鎖すると、コールバック関数が深くネストされ、コードの可読性や保守性が著しく低下します。これを「コールバック地獄」と呼びます。
// コールバック地獄の例
setTimeout(() => {
console.log(1);
setTimeout(() => {
console.log(2);
setTimeout(() => {
console.log(3);
}, 300); // 300ms後に実行
}, 200); // 200ms後に実行
}, 100); // 100ms後に実行
実際に実行した結果(Node.js v26.5.0)は次の通りです。
1
2
3
ネストが1段深くなるごとにインデントが1つ増える様子が分かります。これがさらに5段、10段と続くと、コードの見通しは急速に悪化します。この問題を解決するために、ES2015で Promise が導入されました。
参考
Promiseオブジェクト [ES2015]
Promise は、ES2015(ECMAScript 2015)で導入された、非同期処理の**最終的な完了(または失敗)**とその結果の値を表現するオブジェクトです。非同期処理の状態をラップし、その状態が変化した際に登録されたコールバック関数を呼び出す仕組みを提供します。
Promiseの基本的な使い方
Promise コンストラクタは、resolve と reject という2つの引数を持つ関数を受け取ります。
// asyncPromiseTask関数は、Promiseインスタンスを返す
// 実行結果を再現できるように、Math.random()ではなく明示的な succeed 引数で
// resolve/rejectを切り替えられるようにしている(本記事の全サンプルで共通のパターン)
function asyncPromiseTask(succeed) {
return new Promise((resolve, reject) => {
// ここに非同期処理を記述
// 成功した場合は resolve() を呼び出す
// 失敗した場合は reject(エラーオブジェクト) を呼び出す
setTimeout(() => {
if (succeed) {
resolve("成功しました!");
} else {
reject(new Error("失敗しました..."));
}
}, 200);
});
}
// then() メソッドで、Promiseがresolve(成功)またはreject(失敗)したときに呼ばれるコールバック関数を登録
asyncPromiseTask(true)
.then((result) => {
console.log("成功時の処理:", result);
})
.catch((error) => {
// catch() メソッドで失敗時の処理を登録 (推奨)
console.error("失敗時の処理:", error.message);
})
.finally(() => {
// finally() メソッドで成功/失敗に関わらず実行される処理を登録
console.log("処理が完了しました。");
});
succeed = true で実行した結果:
成功時の処理: 成功しました!
処理が完了しました。
asyncPromiseTask(false) に変えて同じコードを実行すると、then() は呼ばれず catch() が呼ばれます。
失敗時の処理: 失敗しました...
処理が完了しました。
finally() はどちらのケースでも実行されている点に注目してください。
Promiseの状態
Promise インスタンスは、内部的に以下の3つの状態のいずれかをとります。
- Pending (保留): 非同期処理がまだ完了していない初期状態です。
- Fulfilled (成功): 非同期処理が成功し、結果の値が利用可能になった状態です。
resolve()が呼び出されたときにこの状態になります。 - Rejected (失敗): 非同期処理が失敗し、エラーが発生した状態です。
reject()が呼び出されたときにこの状態になります。
一度 Fulfilled または Rejected になった Promise インスタンスは、それ以降別の状態に変化することはありません。
Promiseチェーン
複数の非同期処理を順番に実行したい場合に、Promiseチェーン を利用します。then() メソッドは常に新しい Promise インスタンスを返すため、その返り値に対してさらに then() や catch() メソッドを連結できます。これにより、コールバック地獄を解消し、コードの可読性を向上させます。
Promise.resolve()
.then(() => {
console.log("ステップ1");
return asyncPromiseTask(true); // Promiseを返す
})
.then((result) => {
console.log("ステップ2:", result);
return "次のデータ"; // 値を返す (自動的にPromise.resolveでラップされる)
})
.then((data) => {
console.log("ステップ3:", data);
})
.catch((error) => {
console.error("エラーが発生しました:", error.message);
});
実際に実行した結果:
ステップ1
ステップ2: 成功しました!
ステップ3: 次のデータ
途中の then() が返した値が、そのまま次の then() の引数として渡っていく様子が確認できます。
Promiseの課題
Promise はコールバック地獄を解決しましたが、以下のような課題も残っていました。
- 非同期処理間の連携が
then()メソッドの連鎖となり、同期的なコードに比べて依然として特殊な記述スタイルとなる。 - エラーハンドリングが
try...catch構文とは異なるcatch()メソッドで行われるため、直感的に理解しにくい場合があります。 Promiseはあくまでオブジェクトであり、言語の構文レベルでのサポートではないため、より自然な非同期処理の記述が求められた。
参考
Async/Await [ES2017]
async/await は、Promise をベースにした、非同期処理をより同期的なコードのように記述するためのJavaScriptの構文です。
async 関数
関数の前に async キーワードを付けることで、その関数が非同期関数であることを宣言します。async 関数は、常に Promise インスタンスを返します。
async function doAsync() {
return "値"; // この値は Promise.resolve("値") としてラップされる
}
// 上記は以下とほぼ同義
function doAsyncLegacy() {
return Promise.resolve("値");
}
console.log(doAsync()); // 値そのものではなく Promise が返ることを確認する
doAsync().then((v) => console.log("doAsyncの解決値:", v));
console.log(doAsyncLegacy());
doAsyncLegacy().then((v) => console.log("doAsyncLegacyの解決値:", v));
実際に実行した結果:
Promise { '値' }
Promise { '値' }
doAsyncの解決値: 値
doAsyncLegacyの解決値: 値
console.log(doAsync()) の時点では "値" という文字列ではなく Promise { '値' } というPromiseオブジェクトそのものが出力されており、doAsync 関数と doAsyncLegacy 関数が完全に等価な Promise を返していることが実行結果からも確認できます。
await 式
async 関数内でのみ使用できる await キーワードは、Promise の解決(Fulfilled または Rejected になる)を待つ構文です。await 式を使うことで、非同期処理の完了を待ってから次の行のコードを実行できるため、Promise チェーンで実現していた処理の流れを、より直感的で読みやすい同期的なスタイルで記述できます。
async function asyncMain() {
console.log("処理開始");
// Promiseが解決されるまで待機
const result = await asyncPromiseTask(true); // asyncPromiseTaskはPromiseを返す関数
console.log("Promiseが解決されました:", result);
console.log("処理終了");
}
asyncMain();
実際に実行した結果:
処理開始
Promiseが解決されました: 成功しました!
処理終了
エラーハンドリング
await 式は、Promise が Rejected になった場合にエラーを throw します。これにより、非同期処理のエラーを通常の同期処理と同様に try...catch 構文で捕捉できるようになり、エラーハンドリングが非常にシンプルになります。
async function asyncMainWithErrorHandling(succeed) {
try {
console.log("エラー発生の可能性のある処理開始");
const value = await asyncPromiseTask(succeed); // 失敗する可能性のあるPromise
console.log("成功:", value);
} catch (error) {
console.error("エラーを捕捉しました:", error.message);
} finally {
console.log("finallyブロックが実行されました。");
}
}
asyncMainWithErrorHandling(true);
succeed = true で実行した結果:
エラー発生の可能性のある処理開始
成功: 成功しました!
finallyブロックが実行されました。
asyncMainWithErrorHandling(false) に変えて実行すると、catch ブロックに処理が移ります。
エラー発生の可能性のある処理開始
エラーを捕捉しました: 失敗しました...
finallyブロックが実行されました。
async/await は、JavaScriptの非同期処理を劇的に改善し、より複雑な非同期ロジックも簡潔に記述することを可能にしました。しかし、async/await は Promise の**糖衣構文(syntax sugar)**に過ぎず、実行のタイミングは依然としてイベントループのルールに従います。次章では、そのルールを正確に見ていきます。
イベントループを正確に理解する:コールスタック・マクロタスク・マイクロタスク
「setTimeout(fn, 0) は0ミリ秒後に実行されるのだから、ほぼ即座に実行される」と考えるのは自然な誤解です。しかし実際には、JavaScriptには優先順位の異なる 2種類のタスクキュー が存在し、この誤解を生む原因になっています。
- マクロタスクキュー(Task Queue / Macrotask Queue):
setTimeout、setInterval、I/Oコールバックなどが積まれるキューです。 - マイクロタスクキュー(Microtask Queue):
Promise.then/catch/finallyのコールバックやqueueMicrotask()が積まれるキューです。
そして、両者の間には次の鉄則があります。
コールスタックが空になるたびに、イベントループはマイクロタスクキューが完全に空になるまですべて実行してから、ようやくマクロタスクキューから1個だけ取り出して実行する。
つまり、「マクロタスクを1個実行 → マイクロタスクを全部片付ける → マクロタスクを1個実行 → …」という順序が厳密に守られます。マイクロタスクの実行中に新たなマイクロタスクが登録されても、それも同じラウンドで処理されるため、マイクロタスクが尽きるまでマクロタスクの出番は回ってきません。

実行順序を実際に確認する
setTimeout(fn, 0)、Promise.resolve().then(fn)、そして同期処理を混在させて、実際の実行順序を検証します。
console.log("1: 同期処理(スクリプト先頭)");
setTimeout(() => {
console.log("5: setTimeout(fn, 0) のコールバック(マクロタスク)");
}, 0);
Promise.resolve()
.then(() => {
console.log("3: Promise.then その1(マイクロタスク)");
})
.then(() => {
console.log(
"4: Promise.then その2(マイクロタスク、さらに後ろへ積まれる)",
);
});
queueMicrotask(() => {
console.log("(参考) queueMicrotaskも同じマイクロタスクキューに入る");
});
console.log("2: 同期処理(スクリプト末尾)");
実際に実行した結果:
1: 同期処理(スクリプト先頭)
2: 同期処理(スクリプト末尾)
3: Promise.then その1(マイクロタスク)
(参考) queueMicrotaskも同じマイクロタスクキューに入る
4: Promise.then その2(マイクロタスク、さらに後ろへ積まれる)
5: setTimeout(fn, 0) のコールバック(マクロタスク)
多くの人が「setTimeout(fn, 0) が2番目か3番目に出力される」と予想しますが、実際には最後に出力されます。理由は次の通りです。
console.log("1: ...")が同期的に実行される。setTimeout(fn, 0)が呼ばれると、コールバックはすぐにマクロタスクキューへ登録される(0msでもキューに入る点は変わらない)。Promise.resolve().then(...)のコールバックはマイクロタスクキューへ登録される。queueMicrotask(...)も同様にマイクロタスクキューへ登録される。console.log("2: ...")が同期的に実行され、コールスタックが空になる。- コールスタックが空になったので、イベントループはまずマイクロタスクキューを全部空にする。これが
3→(参考)→4の順に出力される理由(登録順=FIFO)。 - マイクロタスクキューが空になって初めて、イベントループはマクロタスクキューから1個だけ取り出す。ここでようやく
setTimeoutのコールバックが実行され、5が出力される。
これが「setTimeout(fn, 0) は即座には実行されない」という直感に反する挙動の正体です。実務では、Promise チェーンを大量に連結すると、たとえ setTimeout の待機時間が短くても、UIの更新(多くの場合マクロタスク経由)がマイクロタスクの処理完了まで後回しにされる、という形で影響が現れることがあります。
参考
- Tasks, microtasks, queues and schedules | Jake Archibald - マクロタスクとマイクロタスクの優先順位を図解した定番記事です。
- queueMicrotask() - Web API | MDN
- In depth: Microtasks and the JavaScript runtime environment | MDN
エッジケース
理屈の上では理解していても、実際に手を動かして検証すると初めて実感できる落とし穴を3つ取り上げます。
① async 関数のthrow vs .catch()忘れのreject
async 関数の中で throw すると、その例外は自動的に rejectされたPromise に変換されます。
// (a) asyncな関数内でthrowする場合: 例外はPromiseのrejectに変換される
async function asyncThrows() {
throw new Error("asyncThrowsの中で投げた例外");
}
const p = asyncThrows();
console.log(p); // Promise { <rejected> Error: ... } になる
p.catch((e) => console.log("catchで捕捉:", e.message));
実際に実行した結果:
Promise {
<rejected> Error: asyncThrowsの中で投げた例外
at asyncThrows (/path/to/asyncThrows.js:3:9)
...(スタックトレース省略)
}
catchで捕捉: asyncThrowsの中で投げた例外
console.log(p) の出力に <rejected> という状態表示が出ている点に注目してください。.catch() を後から呼んでいるので例外はきちんと捕捉され、プロセスは正常に継続します。
一方、Promise が reject されたにもかかわらず .catch() を一切付け忘れると、話は変わります。
// (b) .catch()を付け忘れてrejectされたPromiseを放置した場合
function rejectingPromise() {
return new Promise((resolve, reject) => {
reject(new Error("catchされなかったエラー"));
});
}
rejectingPromise(); // .catch()も.thenの第2引数もない -- unhandled rejection
console.log("この行は実行される(同期処理は止まらない)");
実際に実行した結果(unhandled_rejection.js という名前で保存して実行):
この行は実行される(同期処理は止まらない)
unhandled_rejection.js:4
reject(new Error("catchされなかったエラー"));
^
Error: catchされなかったエラー
at unhandled_rejection.js:4:12
at new Promise (<anonymous>)
at rejectingPromise (unhandled_rejection.js:3:10)
at Object.<anonymous> (unhandled_rejection.js:8:1)
...(モジュールローダーのスタックフレーム省略)
Node.js v26.5.0
このスクリプトは 終了コード1でクラッシュします。「同期処理は止まらない」というログは出力されるものの、その後イベントループが1周してマイクロタスクの処理が完了した時点でrejectされたPromiseがまだ誰にも捕捉されていないことが検出され、プロセス全体が未捕捉の例外として終了しています。Node.js は v15 以降、unhandledRejection のデフォルト挙動が warn から throw に変更されており、素のNode.jsではunhandled rejectionはプロセスクラッシュを引き起こします(ブラウザではコンソール警告のみで継続する点と対照的です)。この挙動は node --unhandled-rejections=warn フラグで警告のみに緩和できますが、本番運用ではクラッシュさせて早期に気づける状態を保つのが推奨されています。
対策: Promise を返す関数を呼ぶ場所には必ず .catch()(または try...catch で囲んだ await)を付ける。async 関数の戻り値を放置(フローティングPromise)しないよう、ESLintの no-floating-promises(@typescript-eslint)のようなルールで機械的に検出するのも有効です。
② Promise.all vs Promise.allSettled
複数の非同期処理を同時に実行して結果をまとめたい場合、Promise.all() と Promise.allSettled() のどちらを使うかで、1つでも失敗したときの挙動が大きく変わります。
Promise.all() は、配列内のいずれか1つが reject した瞬間、他の結果を待たずに即座に失敗します(fail-fast)。
function delay(ms, value, shouldReject = false) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldReject) {
reject(new Error(value));
} else {
resolve(value);
}
}, ms);
});
}
async function withAll() {
console.log("--- Promise.all ---");
try {
const results = await Promise.all([
delay(100, "A"),
delay(50, "B-fails", true), // 最初に失敗する
delay(300, "C"), // Cはまだ完了していないのにallは既に失敗している
]);
console.log("results:", results);
} catch (error) {
console.error("Promise.all は最初のrejectで即座に失敗:", error.message);
}
}
withAll();
実際に実行した結果:
--- Promise.all ---
Promise.all は最初のrejectで即座に失敗: B-fails
50ms後に B-fails が reject した時点で catch に処理が移っており、100ms後に成功するはずだった A や300ms後に成功するはずだった C の結果は一切参照できません(C はバックグラウンドで実行され続けますが、結果は握りつぶされます)。
一方、Promise.allSettled() はどれか1つが失敗してもすべてのPromiseの決着を待ち、成功・失敗を問わず全件の結果を返します。
async function withAllSettled() {
console.log("--- Promise.allSettled ---");
const t0 = Date.now();
const results = await Promise.allSettled([
delay(100, "A"),
delay(50, "B-fails", true),
delay(300, "C"),
]);
console.log(`経過時間: 約${Date.now() - t0}ms(最も遅いCの300msまで待つ)`);
for (const r of results) {
if (r.status === "fulfilled") {
console.log("fulfilled:", r.value);
} else {
console.log("rejected:", r.reason.message);
}
}
}
withAllSettled();
実際に実行した結果:
--- Promise.allSettled ---
経過時間: 約301ms(最も遅いCの300msまで待つ)
fulfilled: A
rejected: B-fails
fulfilled: C
経過時間が約301msになっている点が Promise.all との決定的な違いです。allSettled は最も遅い C(300ms)の完了まできちんと待ってから、{status, value} または {status, reason} という形式で全件の結果を返しています。「1件でも失敗したら全体を失敗にしたい」なら Promise.all、「失敗の有無に関わらず全件の結果を知りたい」なら Promise.allSettled を使う、という使い分けが重要です。
③ forEach の中で await してもシーケンシャルに待ってくれない
非常によくある実務上のバグです。配列の各要素に対して非同期処理を順番に実行したいとき、Array.prototype.forEach() の中で await を使っても意図通りに待ってくれません。
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
async function fetchItem(id) {
const data = await delay(100, `item-${id}`);
console.log(` fetched: ${data}`);
return data;
}
async function withForEach() {
console.log("--- forEach + await(よくある間違い) ---");
const t0 = Date.now();
[1, 2, 3].forEach(async (id) => {
await fetchItem(id);
});
console.log(
`forEach直後の経過時間: ${Date.now() - t0}ms(★逐次完了を待たずにここに到達する)`,
);
}
実際に実行した結果:
--- forEach + await(よくある間違い) ---
forEach直後の経過時間: 0ms(★逐次完了を待たずにここに到達する)
fetched: item-1
fetched: item-2
fetched: item-3
forEach直後の経過時間 が 0ms と表示されていることに注目してください。forEach はコールバックが返す Promise を一切待たないため、3つの async コールバックが(内部の await の効果はそれぞれのコールバック内では有効なまま)ほぼ同時に開始され、forEach 自体は即座に終了して次の行に進んでしまいます。fetched: のログが3件とも forEach直後の経過時間 より後に出力されているのは、これらが forEach の呼び出し完了後にバックグラウンドで解決したためです。
正しく逐次実行したい場合は、for...of ループ(または通常の for 文)を使います。
async function withForOf() {
console.log("--- for...of + await(正しい書き方) ---");
const t0 = Date.now();
for (const id of [1, 2, 3]) {
await fetchItem(id);
}
console.log(
`for...of完了時の経過時間: ${Date.now() - t0}ms(★3件分、約300ms待ってから到達する)`,
);
}
実際に実行した結果:
--- for...of + await(正しい書き方) ---
fetched: item-1
fetched: item-2
fetched: item-3
for...of完了時の経過時間: 304ms(★3件分、約300ms待ってから到達する)
for...of では各イテレーションの await がループ本体の実行を実際にブロックするため、item-1 → item-2 → item-3 の順に約100msずつ、合計約300msかけて逐次実行され、for...of完了時の経過時間 が304msという実測値に反映されています。
対策: 配列を逐次的に非同期処理したい場合は for...of + await を使う。並行実行して構わない場合は forEach ではなく Promise.all(array.map(async (item) => ...)) を使う(forEach はコールバックの戻り値を無視する設計であり、非同期処理との相性が根本的に悪いため、この用途では選択肢に入れないのが安全です)。
最近の動向:Node.js 26 と V8 14.6
2026年5月にリリースされた Node.js 26 は、V8エンジンを 14.6 に更新しました。この V8 アップデートでは async/await や Promise チェーン、非同期イテレータといった非同期パターンの実行パスが最適化されており、JSONパースなど周辺処理の高速化も報告されています。また、Node.js 26 では日時処理のための Temporal API がデフォルトで有効化されるなど、非同期処理と組み合わせて使われることの多い周辺APIにも刷新が続いています。
await を使うためにわざわざ即時実行の async 関数でラップする、というテクニックも過去のものになりつつあります。ES2022でモジュールのトップレベルに await を書ける「トップレベルawait」がすでに標準化されており、ESモジュール(.mjs や "type": "module" を指定した .js)であれば以下のようにモジュールの先頭で直接 await できます。
// top-level-await-demo.mjs
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
console.log("モジュール読み込み開始");
const value = await delay(50, "トップレベルawaitの結果");
console.log(value);
なお、Node.js は2026年10月にリリース予定のNode.js 27から、メジャーリリースを年2回から年1回に変更し、すべてのリリースをLTS(長期サポート)扱いにする方針を発表しています。バージョン間の非同期関連APIの破壊的変更は今後さらに緩やかになっていく見込みです。
参考
- Node.js — Node.js 26.5.0 (Current) 公式リリースノート
- Node.js v26 Is Here: What Actually Changed | NodeSource
- What’s new in Node.js 26 | Node.js Design Patterns
- Top-level await | V8
関連記事
- Python asyncioによる非同期処理入門 - Pythonにおける非同期処理の仕組みとasync/awaitの使い方
- WebpackとBabelによるReact/TypeScript開発環境の構築方法 - JavaScriptエコシステムの実践的な開発環境構築
関連ツール
- JSON整形ツール(DevToolBox) - JSONデータの整形・検証ツール