- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
今回は「いつ結果が返るか」という約束が変わる場合を見ます。
まず結論
「待たない」には2つあります。呼ばれた側が自分の中で待ち方を工夫するだけのものと、呼ぶ側への約束そのものを変えるもの。前者は外から見て何も変わらず、後者は応答の意味が変わります。
後者を規定しているのが HTTP の 202 で、RFC 9110 はこれを intentionally noncommittal と書いています。202 も 2xx なので規格上は成功です。変わるのは成功が指している対象のほうです。
3秒かかる同じ仕事を持つサーバが2台。1台は非同期 I/O で待ち、もう1台は塞いで待ちます。1本だけ要求を出したクライアントから、この2台は区別できるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
まず「非同期」という語を置いておく
この教材では既に「非同期」が3つの別の意味で出ています。アプリ層編『ランタイム — Node.js は1スレッドで「待つ」をどう扱うか』はランタイムの非同期 I/O、分散編『複製には遅れがある — 書いた直後に読めないことがある』は返事を待たずにコミットする複製、分散編『CAP は3つから2つではない — 定理が二択にしているのはどれか』の純粋非同期モデルは時計もタイムアウトも無い理論モデル。3つ目は特に別物で、システムを記述するモデルの名です。
そこで今回は「非同期」を主題語にせず、「待たない」と言って誰が待たないのかを添えます。 区別したいのは2つ。
- 呼ばれた側が、自分の中で待ち方を変える — 待ちを他へ預け、同じスレッドで別の仕事も進める
- 呼ばれた側が、呼ぶ側への約束を変える — 受け付けたことだけを返し、処理はそのあと進む
1は実装の都合で、呼ぶ側のコードは1文字も変わりません。 2は結果がその場で来ないので、呼ぶ側に取りに行く経路が要ります。同じ「非同期にしました」がどちらでも出ます。
規格が「何も約束しない」と書いている
2には HTTP 側に規定があります。RFC 9110(STD 97、2022年6月)§15.3.3 から逐語で。
The 202 (Accepted) status code indicates that the request has beenaccepted for processing, but the processing has not been completed.The request might or might not eventually be acted upon, as it mightbe disallowed when processing actually takes place. There is nofacility in HTTP for re-sending a status code from an asynchronousoperation.The 202 response is intentionally noncommittal.
読むべきは4点。
the processing has not been completed— 処理は終わっていないThe request might or might not eventually be acted upon— 結局実行されないこともある。 受け付けたことは、やると約束したことですらありませんThe 202 response is intentionally noncommittal— 規格自身が「意図的に何も約束していない」と言っていますThere is no facility in HTTP for re-sending a status code from an asynchronous operation— 結果が同じ応答で返らないのは、実装の手抜きではなくプロトコルにその機能が無いから
ただし同じ節は、応答が status monitor を指す(または埋め込む)表現を返すべきだ(ought to)とも書いています。結果を知る手段が無いのではなく、別途取りに行く先を示す、が正確です。
RFC 9110 §15.3.3 が 202 について intentionally noncommittal と書いている、その意味に最も近いのはどれですか。
202 は成功である。変わるのは成功が指している対象
「202 は成功ではない」と書くと規格と食い違います。 §15.3 は 2xx をこう定めています。
The 2xx (Successful) class of status code indicates that the client's request was successfully received, understood, and accepted.
202 もこのクラスの一員で、規格の定義上まぎれもなく成功です。 変わっているのはそこではありません。
- ・逐語は RFC 9110(STD 97、2022年6月)。取得 2026-09-18
- ・202 も 2xx なので成功。違うのは指す対象
§15.3.1 の 200 は the request has succeeded で、同節の表がメソッドごとに本文の表現を定めます。GET なら対象リソースの表現、POST なら the status of, or results obtained from, the action。200 の本文は結果を運ぶ場所として規定されているわけです。202 にその規定はありません。
正しくは「202 は成功ではない」ではなく、**「202 の成功は受理までを指し、処理まで指さない」**です。
1本のクライアントから、3つのサーバを叩いてみる
実際に測りました。同一プロセス内に3つのサーバを立て、「3秒かかる仕事」は3つとも同じ。変えたのは待ち方だけ。
- ① ランタイムが待たない(
awaitするが JS スレッドは塞がない) - ② 受け付けるだけ(
awaitしない。応答と仕事が別々に進む) - ③ 対照: ビジーループで塞いで待つ
環境は Docker Engine 28.4.0(Docker Desktop) / linux/arm64 / kernel 6.10.14-linuxkit、コンテナは node:24-alpine、Node v24.21.0 / curl 8.22.0。すべて 127.0.0.1 の自前サーバ、各1回の計測。ホストは Apple Silicon の macOS で、Docker Desktop の Linux VM 上の値です。
-- port 5001 ① ランタイムが待たない --HTTP/1.1 200 OK[status=200 time_total=3.018393s]body: {"id":"async-runtime","result":42}-- port 5002 ② 受け付けるだけ --HTTP/1.1 202 Acceptedlocation: /jobs/1[status=202 time_total=0.001710s]body: {"status":"accepted","monitor":"/jobs/1"}-- port 5003 ③ 塞いで待つ --HTTP/1.1 200 OK[status=200 time_total=3.000238s]body: {"id":"blocking","result":42}
まず①と③。200 / 約3秒 / result:42。クライアントからこの2つは区別できません。 スレッドを塞ぐか待ちを預けるかは違いますが、それはサーバ側の都合で、この1本へ渡したものは同じです。
②だけが違います。0.0017 秒で 202、本文に result はなく取りに行く先だけ。ただし この桁は同一プロセス内の localhost の値で、「202 は一瞬で返る」と一般化しないでください。location: /jobs/1 は検証者が一例として付けたもので、規格が Location を要求しているわけではありません。
2026-09-18T02:48:44.331Z recv async-runtime2026-09-18T02:48:47.339Z done async-runtime2026-09-18T02:48:47.350Z recv accepted2026-09-18T02:48:47.356Z recv blocking2026-09-18T02:48:50.356Z done accepted
②が速いだけでは、仕事が軽かった場合と区別できません。決め手はサーバ側のログです。recv accepted が 47.350、done accepted が 50.356。202 を返した 3.006 秒あとに仕事が終わっています。 ②のクライアントは、仕事が終わる3秒前に「成功」を受け取って立ち去っている。①と③は逆で done async-runtime が 47.339、応答が返った時点で仕事は終わっています。
「202 は成功ではない」という説明は、RFC 9110 に照らして妥当ですか。
内側の待ち方だけを変えた場合
待ちを預けても塞いで待っても、応答は 200 で本文に結果が入ります。実測でこの2つはクライアントから区別できませんでした。
状態 時間 本文の result 応答時点で仕事は① 預ける 200 3.018s ある 終わっている③ 塞ぐ 200 3.000s ある 終わっている
呼ぶ側への約束を変えた場合
202 は受理までを成功と言い、処理の完了は言いません。呼ぶ側は結果をその場で受け取れないので、取りに行く経路と、行かなかったときの扱いを書き足すことになります。
状態 時間 本文の result 応答時点で仕事は② 受理 202 0.0017s 無い 終わっていない(取りに行く先だけ)
3つとも「3秒かかる同じ仕事」です。それなのに呼ぶ側のコードを書き換える必要があるのは②だけ。何が違うのでしょう。
呼ぶ側が応答を受け取った瞬間に何を知っていて何を知らないかを、3つそれぞれ言葉にしてください。
この章のまとめ
- 「待たない」には2つある。内側の待ち方を変えるものと、呼ぶ側への約束を変えるもの。前者は呼ぶ側のコードを変えない。
- 実測では、待ちを預ける版(200 / 3.018s / 結果あり)と塞いで待つ版(200 / 3.000s / 結果あり)を区別できなかった。②の 202 版だけが結果を返さず 0.0017 秒で戻った(同一プロセス内の localhost、各1回)。決め手は速さではなくサーバ側のログ。202 を返した 3.006 秒あとに仕事が終わっていた。
- §15.3.3 は 202 を
accepted for processing, but the processing has not been completedと定め、intentionally noncommittalと明記。The request might or might not eventually be acted uponともあり、実行されるかも確定しない。 - 202 も 2xx なので規格上は成功。 §15.3 の 2xx は
successfully received, understood, and accepted。違うのは成功が指す対象で、200 は処理まで、202 は受理まで。 - 結果を送り直す
facilityは HTTP に無く、202 の応答はstatus monitorを指すべき(ought to)とされる。 - 「非同期にした」と報告されたら状態コードと本文を見る。どちらの「待たない」かがそこに出ている。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
実測で②の回が「約束を変えている」と言える決め手になったのは、どの観測ですか。
用語集(4語)
- 202 Accepted
- RFC 9110 §15.3.3 の状態コード。要求を処理のために受理したが処理は完了していない、の意。同じ節が intentionally noncommittal と明記し、処理時に拒否される可能性も残す。
- 2xx (Successful)
- RFC 9110 §15.3 のクラス。要求が successfully received, understood, and accepted であることを示す。200 も 202 もこのクラスに属する。
- status monitor
- RFC 9110 §15.3.3 の語。202 の応答が指す(または埋め込む)べきものとされる、要求の現状を示す先。規格は具体的なヘッダやURLの形を定めていない。
- 2つの「待たない」
- 本教材での言い方。呼ばれた側の実装としての「待たない」は待ちを他へ預けること。呼ぶ側への約束としての「待たない」は、処理が終わる前に応答を返し結果を取りに来てもらうこと。前者は状態コードと本文を変えない。