Vue Fes Japan 2026で「あなたの await、OS まで届いてる? strace で覗く JavaScript の非同期」というタイトルで登壇しました

Vue Fes Japan 2026で「あなたの await、OS まで届いてる? strace で覗く JavaScript の非同期」というタイトルで登壇しました

Vue Fes Japan 2026 のライトニングトークで登壇した「あなたの await、OS まで届いてる? strace で覗く JavaScript の非同期」のスライド資料と、伝えたかったことを紹介しています。
2026.10.25

こんにちは、製造ビジネステクノロジー部のはすとです。

2026年10月24日に大手町プレイス ホール&カンファレンスで開催されたVue Fes Japan 2026のライトニングトーク枠で、「あなたの await、OS まで届いてる? strace で覗く JavaScript の非同期」というタイトルで5分間お話ししました。本記事では、登壇資料と伝えたかったことをまとめます。

Vue Fes Japan 2026とは

https://vuefes.jp/2026/

Vue Fes Japanは、Vue.js日本ユーザーグループが主催するフロントエンドのカンファレンスです。公式サイトでは「日本最大級のフロントエンドカンファレンス」と紹介されており、Vue.jsに閉じず、JavaScriptコミュニティで活躍するOSS開発者が集まるお祭りのようなイベントです!

登壇資料

awaitはPromiseを待つだけでOSには届かず、OSに届いていたのはfetch()が返すPromiseを解決するための処理だった、という話です。スライドに載せたログとコードは次のリポジトリに置いています。Dockerがあればdocker compose up --buildで全部の実験を再現できます。

https://github.com/HasutoSasaki/vuefes-2026-await-strace

伝えたかったこと

await fetch()と書いたとき、待っている間にOSは何をしているのか。これをstraceで確かめるという内容で、3つの問いで進めました。

計測はNode.js v22.23.3とstrace 6.1で、手元のMacのDocker(Colima)上のDebian 12で行いました。
epoll_pwaitはLinuxの仕組みで、macOSではkqueue、WindowsではIOCPをlibuvが使います。スライドのログはどれもLinuxでの話です。

await した時に、OS には何が届くのか

await fetch()をMARKで挟んでstraceを取ると、socket()でソケットを作り、connect()で接続を始め、write()でGETリクエストを書き込んだあと、Node.jsのスレッドはepoll_pwait()の中で応答が届くまで止まります。届いたらread()で読みます。fetch()が返すPromiseを解決するための処理が、OSまで届いていました。

await Promise.resolve()に置き換えると、MARKの間には何も出ません。awaitはPromiseを待つだけで、OSには何も届いていませんでした。

システムコールを出しているのはlibuvです。libuvはイベントループのpollフェーズで、作ったソケットをepollに登録してからepoll_pwaitを呼びます。epoll_pwaitは、登録したソケットのどれかに応答が届くまでOSに待ってもらう仕組みです。応答が届くとOSがepoll_pwaitを戻し、libuvがreadして、Promiseが解決し、awaitの続きが動きます。

直列と Promise.all は、OS から見ると何が違うのか

ここからは、モックサーバーが300ms待ってから応答します。直列(await fetch(); await fetch())は、1本目のソケットだけを登録してepoll_pwaitで待ち、2本目のsocketとconnectは1本目の応答を読んでから出ています。

Promise.allは、2本ともソケットを作って接続を始めてからepoll_pwaitに入ります。epoll_pwaitは1回で、そのあと2本の応答を読んでいます。

所要時間は直列が約640ms、Promise.allが約330msで、約半分でした。Promise.allが速いのは、awaitで止まる前に2本ともfetch()を呼んでいるためです。2本とも登録してからepoll_pwaitに入れるため、2本分の待ちが1回の待ちに重なります。

Nuxt の useFetch に置き換えると

NuxtのuseFetchでも同じことを試しました。await useFetchを2回並べると直列と同じ形になり、1本目の応答待ちが終わってから2本目のconnectが出ています。

Promise.allで束ねると、2本ともconnectしてからepoll_pwaitが1回で、同じ形になりました。初回リクエストの所要時間は748msと436msです。

まとめとして話したこと

awaitはPromiseを待つだけで、OSには届きません。OSに届いていたのは、fetch()が返すPromiseを解決するための処理でした。JavaScriptが止まったあとにlibuvがソケット操作を出し、pollフェーズのepoll_pwaitで応答を待っていました。

最後に

Vue Fes Japanという大きな舞台での登壇は初めてで、とても緊張しましたが、楽しかったです!登壇後は、Xや懇親会等で反応をいただけて嬉しかったです。

スライドを作る過程で、自分の理解も整理されました。ログを見ながら「fetch()が返すPromiseを解決するための処理が届く」と言い直す作業を繰り返したことで、イベントループとlibuvの役割分担を自分の言葉で説明できるようになりました。次はもう少し長い枠でも話してみたいと思います。

参考

この記事をシェアする

関連記事