Slide 1

Slide 1 text

あなたの await、OS まで届いてる? strace で覗く JavaScript の非同期 Vue Fes Japan 2026 Sasaki Hasuto © 2026 Classmethod, Inc.

Slide 2

Slide 2 text

非同期処理のおさらい async.mjs async function main() { console.log(1); await fetch('/a'); console.log(3); } main(); console.log(2); node async.mjs の実行結果 1 2 3 © 2026 Classmethod, Inc. 2

Slide 3

Slide 3 text

非同期処理はどこで動いている? イベントループの中だけで完結しているのか。 OS まで関わっているのか。 strace で覗いてみる ※ strace:プログラムが OS に依頼した処理(システムコール)を記録するツール © 2026 Classmethod, Inc. 3

Slide 4

Slide 4 text

確かめ方 marks.mjs process.stdout.write('MARK A\n'); // ここで await する(fetch() / Promise.resolve()) process.stdout.write('MARK B\n'); terminal strace -f -tt -T \ -e trace=socket,connect,epoll_pwait,write,read \ node marks.mjs fetch © 2026 Classmethod, Inc. 4

Slide 5

Slide 5 text

計測した環境 Node.js v22.23.3 / strace 6.1 Debian 12(Linux 6.8、aarch64) 手元の Mac の Docker(Colima)上で計測 ※ epoll_pwait は Linux の仕組み。macOS では kqueue、Windows では IOCP © 2026 Classmethod, Inc. 5

Slide 6

Slide 6 text

await した時に、OS には何が届く? © 2026 Classmethod, Inc. 6

Slide 7

Slide 7 text

await fetch() の場合 marks.mjs fetch process.stdout.write('MARK A\n'); const res = await fetch('/a'); await res.text(); process.stdout.write('MARK B\n'); © 2026 Classmethod, Inc. 7

Slide 8

Slide 8 text

await fetch() の strace strace write(1, "MARK A...") = 7 <0.000015> 通信部分 // TCP socket(AF_INET, SOCK_STREAM|..., IPPROTO_IP) = 21 <0.000027> connect(21, 127.0.0.1:3000) = -1 EINPROGRESS <0.000749> write(21, "GET /a HTTP/1.1...") = 170 <0.000036> // 応答待ち epoll_pwait(15, [{events=EPOLLIN, data={u32=21, u64=21}}], 1024, 482, NULL, 8) = 1 <0.013712> read(21, "HTTP/1.1 200 OK...") = 172 <0.000016> write(1, "MARK B...") = 7 <0.000015> © 2026 Classmethod, Inc. 8

Slide 9

Slide 9 text

await 自体が OS に何か依頼している? marks.mjs resolve process.stdout.write('MARK A\n'); await Promise.resolve(); process.stdout.write('MARK B\n'); strace write(1, "MARK A...") = 7 <0.000017> write(1, "MARK B...") = 7 <0.000019> © 2026 Classmethod, Inc. 9

Slide 10

Slide 10 text

Node.js の中の層 Node.js ( が実⾏) JavaScript V8 undici fetch net の本体 Promise TCP ソケット の続き(マイクロタスク)は V8 が管理 の C++ 層(tcp_wrap) の net と libuv をつなぐ Node JS libuv イベントループ・システムコールを呼ぶ システムコール ( ) OS Linux © 2026 Classmethod, Inc. TCP モックサーバー 10

Slide 11

Slide 11 text

I/O 待ちはどこで起きている? undici libuv のイベントループ timers net Node close pending check idle / prepare の C++ 層(tcp_wrap) libuv (Linux) OS © 2026 Classmethod, Inc. epoll_pwait() poll 11

Slide 12

Slide 12 text

直列と Promise.all OS から見ると何が違う? © 2026 Classmethod, Inc. 12

Slide 13

Slide 13 text

2 パターンの書き方で比べる ここからは、モックサーバーが 300ms 待ってから応答します A 直列 const a = await fetch('/a'); const b = await fetch('/b'); B Promise.all const [a, b] = await Promise.all([ fetch('/a'), fetch('/b'), ]); © 2026 Classmethod, Inc. 13

Slide 14

Slide 14 text

パターンA の strace // 1 A 直列 本目:TCP通信部分 socket(AF_INET, SOCK_STREAM|..., IPPROTO_IP) = 21 <0.000043> connect(21, 127.0.0.1:3000) = -1 EINPROGRESS <0.000086> write(21, "GET /a HTTP/1.1...") = 170 <0.000060> // 1 本目:応答待ち epoll_pwait(15, [...], 1024, 494, NULL, 8) = 1 <0.305597> read(21, "HTTP/1.1 200 OK...") = 172 <0.000071> // 2 本目:TCP通信部分 socket(AF_INET, SOCK_STREAM|..., IPPROTO_IP) = 22 <0.000080> connect(22, 127.0.0.1:3000) = -1 EINPROGRESS <0.000158> write(22, "GET /b HTTP/1.1...") = 170 <0.000090> // 2 本目:応答待ち epoll_pwait(15, [], 1024, 166, NULL, 8) = 0 <0.169480> epoll_pwait(15, [...], 1024, 494, NULL, 8) = 1 <0.124177> read(22, "HTTP/1.1 200 OK...") = 172 <0.000088> © 2026 Classmethod, Inc. 14

Slide 15

Slide 15 text

パターンB の strace B Promise.all 通信部分(2 本) // TCP socket(AF_INET, SOCK_STREAM|..., IPPROTO_IP) = 21 <0.000040> connect(21, 127.0.0.1:3000) = -1 EINPROGRESS <0.000120> socket(AF_INET, SOCK_STREAM|..., IPPROTO_IP) = 22 <0.000030> connect(22, 127.0.0.1:3000) = -1 EINPROGRESS <0.000078> write(21, "GET /a HTTP/1.1...") = 170 <0.000038> write(22, "GET /b HTTP/1.1...") = 170 <0.000034> // 応答待ち epoll_pwait(15, [...], 1024, 493, NULL, 8) = 1 <0.303662> read(21, "HTTP/1.1 200 OK...") = 172 <0.000032> read(22, "HTTP/1.1 200 OK...") = 172 <0.000029> © 2026 Classmethod, Inc. 15

Slide 16

Slide 16 text

A と B で I/O 待ちはどう違う? A 直列(約 640ms) JS fetch('/a') fetch('/b') socket / connect write a libuv () (poll) a の応答待ち socket / connect write b () epoll_pwait OS 300ms B Promise.all JS (poll) b の応答待ち epoll_pwait 300ms (約 330ms) fetch('/a') fetch('/b') libuv OS socket / connect write a b ( と ) epoll_pwait (poll) の応答待ち 300ms b の応答待ち 300ms a 2 本分の待ちが 1 回の I/O 待ちに重なる 時間 © 2026 Classmethod, Inc. 16

Slide 17

Slide 17 text

Nuxt の useFetch に置き換えると? © 2026 Classmethod, Inc. 17

Slide 18

Slide 18 text

Nuxt で await useFetch を 2 回並べる serial.vue const { data: a } = await useFetch('/a') const { data: b } = await useFetch('/b') strace(初回リクエスト 748ms) connect(23, 127.0.0.1:3000) = -1 EINPROGRESS <0.000091> write(23, "GET /a HTTP/1.1...") = 170 <0.000042> epoll_pwait(15, [...], 1024, 495, NULL, 8) = 1 <0.309949> connect(24, 127.0.0.1:3000) = -1 EINPROGRESS <0.000211> write(24, "GET /b HTTP/1.1...") = 170 <0.000117> epoll_pwait(15, [], 1024, 162, NULL, 8) = 0 <0.166521> epoll_pwait(15, [...], 1024, 498, NULL, 8) = 1 <0.136920> © 2026 Classmethod, Inc. 18

Slide 19

Slide 19 text

Nuxt で Promise.all を使って束ねる parallel.vue const [{ data: a }, { data: b }] = await Promise.all([ useFetch('/a'), useFetch('/b'), ]) strace(初回リクエスト 436ms) connect(23, 127.0.0.1:3000) = -1 EINPROGRESS <0.000103> connect(24, 127.0.0.1:3000) = -1 EINPROGRESS <0.000054> write(23, "GET /a HTTP/1.1...") = 170 <0.000041> write(24, "GET /b HTTP/1.1...") = 170 <0.000028> epoll_pwait(15, [...], 1024, 493, NULL, 8) = 1 <0.305665> © 2026 Classmethod, Inc. 19

Slide 20

Slide 20 text

まとめ © 2026 Classmethod, Inc. 20

Slide 21

Slide 21 text

await fetch を strace で覗いた結果 await 自体は OS に届きません。 OS に届いていたのは、fetch が返す Promise を解決するための処理でした。 libuv が出すソケット操作と、poll フェーズで 応答を待つ epoll_pwait です © 2026 Classmethod, Inc. 21

Slide 22

Slide 22 text

皆さんのコードも覗いてみてください 普段書いている何気ないコードも、 strace で覗いてみると、 OS に何を伝えているのかが見えて面白いです © 2026 Classmethod, Inc. 22

Slide 23

Slide 23 text

記事とリポジトリ ログとコードはリポジトリで再現できます 検証一式(GitHub) https://github.com/HasutoSasaki/vuefes-2026-await-stra ce Docker で全実験を再現できます Node.js の非同期処理をシステムコールから覗く Node.js の fetch を strace で追う 複数 fetch の直列 vs Promise.all を strace で比べる JS の非同期処理は epoll で成り立っているのか (Node.js / Deno / Bun) https://dev.classmethod.jp/articles/nodejs-async-d eep-dive-with-syscalls/ https://dev.classmethod.jp/articles/nodejs-multiple -fetch-strace/ © 2026 Classmethod, Inc. https://dev.classmethod.jp/articles/nodejs-fetch-st race/ https://dev.classmethod.jp/articles/hasuto-js-asyn c-epoll-node-deno-bun/ 23

Slide 24

Slide 24 text

ほかにも書いています こんな感じで色々書いているので、興味がある方は見てみてください © 2026 Classmethod, Inc. 24

Slide 25

Slide 25 text

自己紹介 名前:佐々木 蓮斗(ささき はすと) 部署:製造ビジネステクノロジー部 職種:サーバーサイドエンジニア 社内活動:社内 LT 会「LT道場」を運営 趣味:筋トレ、アニメ 最近ベンチ100kg達成しました。 © 2026 Classmethod, Inc. 25

Slide 26

Slide 26 text

No content