JAVASCRIPT V2 · ADVANCED · 132
Advanced Async:單一 Request 都正確,為什麼 UI 還是會錯?
42 與 26 解決單條 Promise pipeline 與 scheduling。真實 UI 的下一層問題是 concurrency:多個 request 同時存在、完成順序不固定、舊 response 晚到、使用者已經不需要原工作。這時 correctness 需要 cancellation 或 version policy。
Learning outcomes
- 區分 sequential dependency 與 concurrent independent work。
- 正確選 Promise.all / allSettled。
- 辨識 stale response race condition。
- 使用 AbortController 取消 fetch。
- 實作 latest-wins/version guard,並理解 cancellation 與忽略 stale result 的差別。
1. Why now:錯誤不一定來自失敗 Request
User types:
j
request A starts
User types:
javascript
request B starts
B finishes first
→ render "javascript"
A finishes later
→ render "j" // stale overwrite
A、B 都可能 HTTP 200;bug 是 completion order 與 user intent 不一致。
2. Mental model:Start Order ≠ Finish Order
start:
A ────────────────┐
B ───────┐ │
↓ ↓
finish:
B A
任何依賴「先發出的 request 一定先回」的 UI 都有 race 風險。
3. Worked Example A:Sequential Await
const user =
await loadUser();
const courses =
await loadCourses(
user.id
);
第二個工作依賴 user.id,因此 sequential 是正確 dependency,而不是效能問題。
4. Worked Example B:Independent Work Concurrent Start
const userPromise =
loadUser();
const settingsPromise =
loadSettings();
const [user, settings] =
await Promise.all([
userPromise,
settingsPromise
]);
兩個工作沒有 dependency,可以同時 start,再一起 await。
5. Promise.all 的 Contract
await Promise.all([
loadA(),
loadB(),
loadC()
]);
任一 input reject,aggregate Promise reject。但其他工作可能已經開始,Promise.all 不會自動取消它們。
6. allSettled:需要每個結果時
const results =
await Promise.allSettled([
loadProfile(),
loadWeather(),
loadNews()
]);
for (const result of results) {
console.log(
result.status
);
}
如果產品允許 widget 部分成功,allSettled 可能比 fail-fast all 更符合 contract。
7. Worked Example C:AbortController
let controller = null;
async function search(
query
) {
controller?.abort();
controller =
new AbortController();
const response =
await fetch(
"/api/search?q="
+ encodeURIComponent(
query
),
{
signal:
controller.signal
}
);
return response.json();
}
取消舊 request 可以節省資源,也能避免部分 stale work。但你仍要正確處理 AbortError。
8. Cancellation 不等於所有 async 工作都真的停止
controller.abort();
AbortSignal 只會影響支援它的 API;你自己啟動的 arbitrary Promise/CPU work 不會因為有 controller 就自動中止。Cancellation 是 cooperative contract。
9. Latest-Wins Version Guard
let version = 0;
async function search(
query
) {
const mine =
++version;
const result =
await load(query);
if (mine !== version) {
return "stale";
}
render(result);
return "rendered";
}
這種 policy 不一定取消舊工作,但能阻止舊結果覆蓋新 state。
10. Execution trace:Race 被 Version 擋住
A starts
mine = 1
B starts
mine = 2
global version = 2
B finishes
2 === 2
→ render B
A finishes
1 !== 2
→ stale, ignore11. Common mistakes
A. 只在 Loading state 禁用 UI 就以為沒有 race
產品可能允許快速切換,或 request 在其他入口重入。
B. AbortError 當真正系統錯誤顯示紅色警告
使用者主動換 query 造成的 abort 通常是預期控制流程。
C. Promise.all 當 cancellation
aggregate reject 不會神奇停止其他 operations。
D. 所有 request 都用 latest-wins
付款、寫入等 domain 可能需要 idempotency/serialization,而不是忽略舊結果。
12. Debug evidence:Record Request Identity
console.log({
requestId,
query,
startedAt,
finishedAt,
currentVersion
});
遇到 UI「偶爾跳回舊資料」,Network timing + request id + render log 往往比只看最後 state 更有證據力。
13. Guided exercise:makeLatestOnly
function makeLatestOnly() {
let version = 0;
return async function (
load
) {
// TODO
};
}
每次呼叫取得自己的 version;只有最新版回傳實際 value,其餘回 "stale"。
14. Independent exercise:Cancelable Search
建立 search controller:每次新 query abort 前一次;HTTP 非 2xx 要 throw;AbortError 不顯示 error UI;真正 network/HTTP failure 才顯示。
try {
// request
} catch (error) {
if (
error.name
=== "AbortError"
) {
return;
}
throw error;
}15. Challenge:Read Race 與 Write Race 不一樣
搜尋結果 stale 可以「忽略舊結果」。如果是「扣款 request timeout 後 retry」,不能靠 latest-wins;你需要思考 server-side idempotency key。說明為什麼 read UI race policy 不能直接套到 money-changing write。
Project checkpoint:Grade Tracker Remote Search
input event
↓
new query version
↓
abort old fetch
↓
start new fetch
↓
response
↓
still latest?
├─ no → ignore
└─ yes → state → render
這把 Stage 4 的 Promise/Event Loop 與 Stage 5 的 closure/version state 串成真正 UI concurrency model。
16. Stage 5 串起來
Function as value
↓
Closure keeps environment
↓
Identity shapes state
↓
RegExp validates text boundary
↓
Date/Intl separates domain/display
↓
this/prototype explains object behavior
↓
immutability clarifies transitions
↓
advanced async protects concurrent state17. Vocabulary / Summary
- concurrency:多個工作在時間上重疊進行。
- race condition:結果依不可保證的 timing/order 而改變。
- stale result:已不符合目前 user intent/state 的舊結果。
- cancellation:要求支援方停止不再需要的工作。
- latest-wins:只接受最新版 request 結果的 policy。
- idempotency:重複執行同一 operation 不造成額外副作用的 contract。
18. Further reading
Knowledge check · Mastery
- 區分 sequential dependency 與 concurrent independent work。
- 解釋 stale response race 的完整 timeline。
- 完成 latest-only 與 cancelable search。
- 完成頁面下方 Executable JS timing tests。