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