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 序列化。

序列化之後,佇列表才真的有意義 —— 第二個事件進來時第一個還沒處理完,它會真的在佇列裡等。

相關

  • as cast 在這裡放大了問題:getOne() 回傳 T | null,as T 把 null 從型別抹掉,TypeScript 不警告,執行時照樣丟錯。cast 不會讓資料變成非 null,只會讓編譯器停止提醒。