[ISSUE-0036] 创建任务 toast 成功但任务列表没增加¶
用户反馈:点击「+创建任务」选了 111923 个地区 → toast 提示「任务已创建(111923 个地区)」→ 但任务列表/KPI 没增加。
用户感知¶
1. 点"创建任务"按钮
2. 配置:关键词 + 国家/州(实际选了所有美国地区 = 111923)
3. 点提交 → toast: ✅ 任务已创建(111923 个地区)
4. ❌ 任务列表无变化,KPI 仍 "1/1"
根因(双重失败)¶
Bug 1:addTask 静默吞错¶
src/utils/task-store.ts:61-69(v0.10.54 之前):
export function addTask(task: MapTask): Promise<void> {
writeChain = writeChain
.then(async () => {
const list = await getTasks();
list.unshift(task);
await taskListItem.setValue(list); // ← 超 chrome.storage 配额会 throw
})
.catch(() => {}); // ❌ 静默吞掉错误
return writeChain;
}
taskListItem.setValue(hugeList) 超 chrome.storage.local 配额 → throw → .catch(() => {}) 吞掉 → caller 拿到 resolved Promise → background await createTask() 也 resolved → return {success: true} → dialog toast 显示成功。
Bug 2:单任务无地区数上限¶
chrome.storage.local:
- 每 item 限制约 5-10MB(具体 Chrome 版本不同)
- 整个 storage 配额约 10MB
111923 个 locationEntries × ~80 字节 = ~9MB,单 item 直接撑爆。
修复(三层)¶
Layer 1:addTask 错误传播¶
export function addTask(task: MapTask): Promise<void> {
// 保留 writeChain 串行写入语义,但错误必须传播
const next = writeChain.then(async () => {
const list = await getTasks();
list.unshift(task);
await taskListItem.setValue(list);
});
// writeChain 自己保持 OK 状态(不影响后续 addTask),但 next 暴露错误给 caller
writeChain = next.catch(() => {});
return next; // ← 这里返回的 promise 会 reject
}
关键技巧:分两个 promise — next 给 caller(保留 reject),writeChain 给后续 chain(吞错防 chain dead)。
Layer 2:background create-task handler try-catch¶
if (type === 'create-task') {
try {
await createTask(message.task || {});
return { success: true };
} catch (e: any) {
console.error('[bg] create-task failed:', e);
return { success: false, message: e?.message || '创建任务失败(storage 可能超配额)' };
}
}
Layer 3:守门 + 友好错误¶
createTask 入口:
const MAX_LOCATIONS_PER_TASK = 50_000;
const locCount = isMulti ? locationEntries.length : locations.length;
if (locCount > MAX_LOCATIONS_PER_TASK) {
throw new Error(
`单任务地区数超上限(${locCount.toLocaleString()} > 50,000)— ` +
`请拆分为多个任务(按国家 / 州分批)。chrome.storage 单 item 限 ~5MB,过大会写失败。`
);
}
Layer 4:dialog 检查返回¶
const resp: any = await browser.runtime.sendMessage({ type: 'create-task', task: ... });
if (resp && resp.success === false) {
notice.error(`创建失败:${resp.message || '未知错误'}`);
return; // 保持 dialog 打开
}
notice.success(`任务已创建(${locationEntries.length} 个地区)`);
修复后流程(111923 地区场景)¶
1. 用户提交 → sendMessage create-task
2. background createTask 入口检查 locCount(111923) > 50000 → throw
3. handler try-catch → return { success: false, message: "单任务地区数超上限..." }
4. dialog 收到 → notice.error("创建失败:单任务地区数超上限...")
5. dialog 不关,用户改小重试
改动文件¶
| 文件 | 改了什么 |
|---|---|
src/utils/task-store.ts |
addTask 错误传播(分 next + writeChain) |
src/entrypoints/background/task-manager.ts |
createTask 入口加 MAX_LOCATIONS_PER_TASK 守门 |
src/entrypoints/background/index.ts |
create-task handler 加 try-catch 返回 success/error |
src/sections/task/task-create-dialog.tsx |
检查 sendMessage 返回,失败 toast.error |
package.json |
0.10.53 → 0.10.54 |
验证¶
- ✅
pnpm compile0 错 - ✅
pnpm build7.52s - 📋 浏览器实测:
- 选 111923 地区 → 应弹 error toast「单任务地区数超上限」
- dialog 不关,用户能改小再试
- 选合理数量(如 1000)→ 正常创建 + 任务列表增加
元-观察:连续 3 个用户使用反馈¶
v0.10.52 ISSUE-0034: Failed to fetch 冒 chrome 错误页
v0.10.53 ISSUE-0035: 按钮无反应(storage API 混用)
v0.10.54 ISSUE-0036: toast 成功但实际失败(addTask 吞错 + 无守门)
5 轮 agent + 5 工具 + 5 rule 收敛后,3 个用户使用反馈连击找到 bug。模式: - ISSUE-0034: 副作用层(chrome 错误页 UX) - ISSUE-0035: API 一致性(两套合法 API 混用) - ISSUE-0036: 错误传播(静默吞错 + 缺守门)
这些都是代码看起来正确但实际行为不对的 bug — audit 找不到,必须真实使用。
audit_grep 防同款¶
audit_grep:
- pattern: "writeChain\\s*=\\s*writeChain\\s*\\.\\s*then.*\\.\\s*catch\\(\\(\\)\\s*=>\\s*\\{\\}\\)"
description: "writeChain 静默吞错模式"
未来如果有人写新的 writeChain = writeChain.then(...).catch(() => {}),
pnpm scan:issue-coverage 会扫到提示。
如何避免再犯¶
- storage write 不要静默吞错 — caller 需要知道是否真成功
- 跨 sendMessage 边界的操作必须检查 response.success — 不要假设成功
- 数据量上限要在入口守门 — 不要让超大 payload 一路传到 storage 才报错
- 写 toast 之前 await 完整链路结果 — 不要 await 一半就 toast
相关¶
- [[0035-create-task-btn-storage-api-mix|0035-去创建任务按钮无反应-storage-api混用]] — 同期用户实测反馈
- chrome.storage.local 配额限制:每 item 约 5-10MB,整库约 10MB