用一條 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 表。

相關