用一條 Promise 鏈把事件 handler 序列化
把一個變數指向鏈的最後一個 Promise,每次有新工作就在它上面登記,然後把 .then() 回傳的新 Promise 存回那個變數。新工作因此必須等上一個工作完成。
let queue: Promise<void> = Promise.resolve();
rl.on("line", (line) => {
queue = queue.then(() => handleLine(line)).catch((error) => console.error(error));
});
async function handleLine(line: string) { /* … */ }前提:Promise 不是工作,是結果
Promise 沒有 start()。你不能啟動一個 Promise。
Promise.resolve()建立出來就已完成。new Promise(executor)的executor在建立的那一刻同步執行。- async function 一被呼叫就開始跑,回傳的 Promise 只是那次執行的結果。
工作在你呼叫函式的那一刻開始,和 Promise 無關。
規則 A:變數只記住位置
queue 記住一件事:鏈的最後一個 Promise 是哪一個物件。新工作要登記在那個物件上,所以程式需要知道它是誰。
賦值不產生順序。
規則 B:.then 回傳的 Promise 會等 callback 回傳的 Promise
這個規則產生順序。
const P1 = P0.then(() => handleLine("第一句"))handleLine 回傳一個 Promise(inner1)。規則是:callback 回傳 Promise 時,P1 要等那個 Promise 完成。
所以 P1 完成的意思不是「callback 被呼叫了」,而是「第一句全部處理完了」。
時間軸
t=0 queue 指向 P0(Promise.resolve(),已完成)
t=0 第一行進來
P1 = P0.then(cb1),cb1 登記在 P0 上
queue = P1
P0 已完成 → 下一個 microtask 呼叫 cb1
cb1 呼叫 handleLine("第一句") → 得到 inner1
P1 等 inner1 完成 ← 規則 B
t=0.3 第二行進來(第一句還在執行)
P2 = P1.then(cb2),cb2 登記在 P1 上
queue = P2
P1 還沒完成 → cb2 不執行
t=2.5 inner1 完成 → P1 完成 ← 規則 B
P1 執行自己的 callback 清單 → 呼叫 cb2
handleLine("第二句") 現在開始
規則 A 只決定 cb2 登記在 P1 而不是 P0。
反例:callback 不回傳 Promise 就失效
// ✅ 沒有大括號,隱式回傳 handleLine(line)
queue = queue.then(() => handleLine(line))
// ❌ 有大括號但沒 return,回傳 undefined
queue = queue.then(() => { handleLine(line) })第二版:handleLine 照樣被呼叫、工作照樣開始,但 callback 回傳 undefined,P1 在 cb1 結束時立即完成,不等第一句做完。cb2 立即被呼叫,序列化失效。
這個失效不產生任何錯誤訊息。
不是容器
Promise 鏈查不到「有誰在排隊」。沒有 length,也不能把項目取出來。要知道佇列內容必須另外存一份。
opencode 就是這樣做:KeyedMutex 鎖住同一個 session 的操作(packages/core/src/session/inbox.ts:59-63),排隊的內容另外存在 session_inbox 表。