我花了 12 個小時才找到的 JavaScript Bug(五秒鐘就能修復)
給你講個故事。
某個平平無奇的周二晚上,我正準備為一個自由職業客戶的 CRM 項目打最后的補丁——一個輕量級的用戶管理后臺,核心功能是:
表單提交 → 調用 API → 更新數據庫 → 發送通知。
我測試了所有流程,通通通過,沒問題。
于是我信心滿滿地把代碼推送到了預發布環境。刷新頁面,填完表單,點擊提交。
然后……啥都沒發生。
- ? 沒提示成功
- ? 沒報錯
- ?? 按鈕失效,控制臺安靜得像凌晨三點的高速公路
我開始陷入調試地獄,整整 12 小時。
最后的真相?
只是一個 return 忘了寫。
看起來“完美”的代碼,其實藏了個坑
我寫的是這樣的邏輯:
function handleFormSubmission(data) {
validateForm(data)
.then(() => {
saveToDatabase(data);
})
.then(() => {
sendNotification(data.user);
})
.catch((err) => {
console.error('Error handling form submission:', err);
});
}
你可能覺得:“這不就標準 Promise 鏈嗎?驗證 → 保存 → 通知。”
但 JavaScript 并不這么理解。
真正發生了什么?
錯誤在于:我沒有 return saveToDatabase()
和 sendNotification()
換句話說,鏈條并沒有等待保存操作完成就執行下一個操作:
validateForm(data)
.then(() => {
saveToDatabase(data); // ? 不是一個返回的 Promise
})
.then(() => {
sendNotification(data.user); // ?? 提前執行了!
});
因為沒有 return
,鏈條在保存還沒結束時就繼續執行了通知邏輯。
結果是:數據庫還沒寫入,通知就已經發出(失敗),但又沒有拋出任何異常。
沒有報錯,沒有異常,只是“悄悄”失效。
那么,5 秒鐘的修復是什么?
只加了兩個 return
:
function handleFormSubmission(data) {
validateForm(data)
.then(() => {
return saveToDatabase(data); // ? 添加 return
})
.then(() => {
return sendNotification(data.user); // ? 再添加 return
})
.catch((err) => {
console.error('Error handling form submission:', err);
});
}
或者更優雅地,直接用 async/await
寫:
async function handleFormSubmission(data) {
try {
await validateForm(data);
await saveToDatabase(data);
await sendNotification(data.user);
} catch (err) {
console.error('Error handling form submission:', err);
}
}
清晰、線性、可靠,眼睛一看就懂了執行順序。
我是怎么忽略這個低級錯誤的?
最可怕的是:
我不是 JS 新手,寫了好多年的異步代碼。
但在那一刻,我居然忘了 Promise 最基本的一條規則:
“如果你不 return 一個 Promise,它就不會等待。”
這就是這類 bug 的可怕之處:
- 不報錯
- 不崩潰
- 不拋異常
- 就只是……靜悄悄地“失效”了
這次踩坑后,我總結了幾條“寫進腦子里”的原則:
一定要 return Promise
.then(() => return asyncFunc())
不寫 return,鏈就斷了,后面的邏輯執行時機全亂。
更推薦用 async/await
- 更好讀
- 更好 debug
- 沒有 “我 return 了沒?” 的焦慮
await stepOne();
await stepTwo();
await stepThree();
清晰得像小說章節。
最難發現的,是“沉默”的 bug
沒有異常提示,就不會引起你警覺。
所以:調試異步代碼時要多加 console.log() 當“路標”。
別太相信“這段代碼看起來沒問題”
越熟練,你越容易放松警惕。
但很多時候,“小小的假設”才是 bug 的源頭。
排查 JavaScript 異步 bug?
- 所有
.then()
都要 return Promise - 更推薦使用 async/await
- try/catch 包圍整個函數邏輯,不只包一行
- debug 時,每一步都打 log 當“路標”
- 一定寫 .catch(),處理意外中斷
總結:
只漏了一個 return
,但讓我浪費了 12 小時。
不過這就是編程的魅力——魔鬼在細節中,而細節決定一切。
它讓我再次意識到:寫代碼不能靠感覺,必須靠確認。 寫異步邏輯時,更要用結構“約束”正確的執行順序。