第66話で、イトは全部のelementを32px、640px幅にするruleを捨てた。
selectorを役割ごとに分ける。
wideとnarrowを両方見る。
見学者のスマートフォンでも、状態と二つのactionがscreen内へ収まった。
一人目の見学者が、青いrefresh status buttonへ触れた。
buttonは押し込まれた色になった。
指を離すと、元の色へ戻った。
それだけだった。
status: closedの横にある更新時刻は、古いまま動かない。
見学者は、もう一度押そうとした。
イトも反射的に、同じbuttonへ指を伸ばした。
見た目がbuttonでも、処理はつながっていない
event monitorには、一行だけ出た。
click occurrence: 1
registered application listener: 0
handler invocation: 0
request sent: 0
DOM update: 0
browserは、buttonでclickが起きたことをeventとして扱える。
けれど、そのeventを観察するapplication listenerが登録されていなかった。
呼び出すcallbackもない。
だから、見た目は反応しても、statusを取りに行く処理は始まらない。
イト
押した色にはなったのに、JavaScriptは何もしてないの?
ピコ
clickが起きたことと、applicationのhandlerが呼ばれたことは別に数えられるよ
ユイ
反応がないからと、もう一度押してよいかも別の判断ですね
次の見学説明まで、あと四十五秒。
温室の状態pageには、最新値を一度読み直した証拠が必要だった。
四十五秒前の三択
操作台に三つの案が並んだ。
A. 反応するまでbuttonを押し、page全体もreloadする
どのclickが処理へ届いたか分からないまま、操作回数を増やす。
listenerが0なら、連打してもapplication handlerは0のまま。
B. clickした瞬間に「更新成功」と表示し、裏でrequestを送る
画面はすぐ変わる。
しかしresponseを受け取る前に成功を見せるため、失敗しても成功表示が残り得る。
C. listenerを一つ結び、pendingを先に置いてresponse後にDOMを更新する
最初のclickでpendingへ移り、同じ処理を重ねて始めない。
requestを一回送り、responseを確認してから意図したDOMだけを変える。
ピコはlistener socketを照らしたが、線をつながなかった。
ユイは「成功をいつ表示するか」とだけnoteへ書き、答えを選ばなかった。
イトは二つ目の指をbuttonから離した。
「Cにする。もう一度押す前に、一回のclickがどこまで届くかつなぐ」
連打counterを0へ戻し、listenerを一つだけ登録した。
event targetへlistenerを結ぶ
buttonはeventのtargetになれるobjectだった。
JavaScriptはaddEventListener()で、特定typeのeventを観察するlistenerを登録できる。
refreshButton.addEventListener("click", refreshStatus);
ここではtypeがclick。
callbackがrefreshStatus。
click eventがtargetへdispatchされると、登録されたcallbackがhandlerとして呼ばれる。
HTMLのbutton elementと、handler functionは同じものではない。
CSSでbuttonらしく見せたこととも別だった。
target、event type、listener、callbackを分けると、clickがどこで途切れたかを追える。
待っている間を先に決める
イトはrequestを送る行より前に、pendingの扱いを書いた。
let pending = false;
async function refreshStatus() {
if (pending) return;
pending = true;
refreshButton.disabled = true;
refreshButton.setAttribute("aria-busy", "true");
try {
const response = await fetch("/api/roof/status");
if (!response.ok) throw new Error("status request failed");
const data = await response.json();
statusText.textContent = data.state;
statusCard.dataset.state = data.state;
} finally {
pending = false;
refreshButton.disabled = false;
refreshButton.removeAttribute("aria-busy");
}
}
pendingがtrueなら、同じhandlerを重ねて開始しない。
最初の処理が終わるまでbuttonもdisabledにする。
「先にrequestを送り、あとで連打を止める」ではない。
requestより前に、いま処理中だと決める。
失敗してもfinallyでpendingとbuttonを戻す。
ただし、このfixtureは考え方を観察するための閉じた実験だった。
production credentialも、実際の温室commandも使っていない。
requestとresponseを成功表示の間に置く
最初のclickがdispatchされた。
listenerはrefreshStatusを一回呼んだ。
handlerはpendingをtrueにし、status endpointへ一回のrequestを作った。
responseが返るまで450ms。
その80ms後、二度目のtapが試された。
buttonはすでにdisabledだったため、二つ目のapplication処理は始まらなかった。
closed fixtureのresponseはこうだった。
status: 200
media type: application/json
body state: closed
body version: 18
handlerはstatusだけで成功と決めなかった。
responseが成功範囲か、JSONとして読めるか、必要なstateがあるかを確認した。
それから初めてDOMを更新した。
status textをclosedへ。
cardのdata-stateもclosedへ。
page全体はreloadしていない。
意図した二nodeを、一つのupdate batchとして変えた。

clickと結果を別々に数える
最終monitorには、途中の段階が別々に残った。
physical tap attempts: 2
dispatched click events: 1
handler invocations: 1
requests sent: 1
responses accepted: 1
DOM update batches: 1
DOM nodes changed: 2
duplicate requests: 0
full page reloads: 0
buttonを二回触ろうとしたことと、requestが二回送られたことは同じではない。
click eventが起きたことと、handlerが呼ばれたことも同じではない。
handlerが動いたことと、成功responseが返ったことも同じではない。
段階を分けて数えると、「JavaScriptが動かない」という一言を切り分けられる。
listenerがないのか。
handlerの入口で止まったのか。
requestが送られていないのか。
responseが失敗したのか。
DOM targetが違うのか。
連打では、その境界が見えなくなる。
一本のevent routeとcountを残せば、次に見る場所が決まる。
最初にする一手
buttonが反応しないときは、連打や成功表示の先出しを止め、まず一回のeventがtargetへdispatchされ、期待するlistenerとhandlerが一回呼ばれたかを見る。
追加requestが必要なら、送信前にpending状態を置き、同じ処理の重複開始を止める。
responseを確認してから、更新対象のDOM nodeだけを変える。
click、handler、request、response、DOM updateのどこから数が合わないか分からない場合は、成功と決めずその境界で止める。
次回:DOMは変わった。screenはいつ変わる?
monitor上のDOM treeでは、status textが新しいnode valueへ変わった。
data attributeも更新されている。
それなのに、wall screenを高速cameraで切り取った一frameには、古い文字がまだ残っていた。
次のframeでは、新しい文字へ変わった。
JavaScriptはDOMを更新した。
しかし、DOMの変化がそのまま同じ瞬間のpixelになるわけではない。
イトは、DOM treeとwall screenの間にある未接続の工程を見た。
次回、ピコルート第68話。
イトは、古い一frameをDOM失敗だと決めなかった。

