async 事件 handler 不會被事件源等待
emitter.on("x", async fn) 的 fn 在第一個 await 停止,並立即回傳一個 Promise。事件源不處理這個 Promise:不等待,也不排隊。下一個事件到達時,事件源再呼叫一次 fn。
兩個 handler 因此同時執行。
併發不需要第二個使用者
單一使用者、單一 process、單一 session 就會發生。條件只有兩個:有 await,而且事件源會重複觸發。
最容易觸發的情況不是「打字很快」,是貼上多行:stdin 收到 "a\nb\n",readline 連續發出兩個 line 事件,中間沒有空隙。
兩種後果
1. 共用資源被互相破壞。
handler A 在等 API 回應時,handler B 動了 A 正在用的資料。我在自己的 harness 上實測到:B 的 insertRow 刪掉該 session 的全部 inbox 列(包含 A 的),A 回來找自己的列得到 null,然後丟出 TypeError。
2. 累積結果的順序錯亂。
handler 依完成順序寫入共用陣列。第二個比第一個早回時,對話歷史變成 [user1, user2, model2, model1],問答對不上。這個後果不會崩潰,所以更難發現。
解法是序列化,不是阻止輸入
不要禁止使用者送下一個事件。讓事件排隊,由單一執行者依序取用:用一條 Promise 鏈把事件 handler 序列化。
序列化之後,佇列表才真的有意義 —— 第二個事件進來時第一個還沒處理完,它會真的在佇列裡等。
相關
ascast 在這裡放大了問題:getOne()回傳T | null,as T把 null 從型別抹掉,TypeScript 不警告,執行時照樣丟錯。cast 不會讓資料變成非 null,只會讓編譯器停止提醒。