做趣味圖形的網(wǎng)站seo引擎搜索
文章目錄
- 異常處理方案
- 同步代碼的異常處理
- Promise 的異常處理
- async await 的異常處理
感謝閱讀,覺(jué)得有幫助可以點(diǎn)點(diǎn)關(guān)注點(diǎn)點(diǎn)贊,謝謝!
異常處理方案
在JS開(kāi)發(fā)中,處理異常包括兩步:先拋出異常,然后捕獲異常。
為什么要做異常處理
異常處理非常重要,至少有以下幾個(gè)原因:
1.防止程序報(bào)錯(cuò)甚至停止運(yùn)行:當(dāng)代碼執(zhí)行過(guò)程中發(fā)生錯(cuò)誤或異常時(shí),如果沒(méi)有適當(dāng)?shù)漠惓L幚頇C(jī)制,程序可能會(huì)報(bào)錯(cuò)、停止運(yùn)行,甚至崩潰。通過(guò)處理異常,我們可以捕獲錯(cuò)誤并采取適當(dāng)?shù)拇胧┍苊庀到y(tǒng)報(bào)錯(cuò)。
2.錯(cuò)誤排查和調(diào)試:異常處理有助于定位和排查錯(cuò)誤??梢酝ㄟ^(guò)捕獲異常并輸出相關(guān)信息,比如打印日志、錯(cuò)誤上報(bào)、跟蹤堆棧等等,以便快速定位問(wèn)題所在,并進(jìn)行調(diào)試和修復(fù)。
3.提高代碼健壯性和可靠性:可以采取適當(dāng)?shù)拇胧┨幚頋撛诘漠惓G闆r,從而減少程序出錯(cuò)的可能性。
4.提升用戶體驗(yàn):通過(guò)兜底、容錯(cuò)、容災(zāi)等異常處理方案,可以向用戶提供有效的錯(cuò)誤信息提示,而不是讓用戶界面無(wú)響應(yīng)甚至白屏。
拋出異常
拋出異常的使用場(chǎng)景舉例:
我們經(jīng)常會(huì)封裝一些工具函數(shù),這些函數(shù)可能給自己用,也可能給外部團(tuán)隊(duì)用。
在函數(shù)內(nèi)部,如果不符合預(yù)期的業(yè)務(wù)邏輯,或者遇到異常情況時(shí),很多人的寫(xiě)法是直接 return,不往下執(zhí)行了。但是 return 的寫(xiě)法存在一個(gè)很大的弊端:調(diào)用者不知道是因?yàn)楹瘮?shù)內(nèi)部沒(méi)有正常執(zhí)行,還是執(zhí)行的返回結(jié)果就是一個(gè)undefined。return 的寫(xiě)法只是規(guī)避了問(wèn)題,沒(méi)有解決問(wèn)題。建議的做法是:我們需要手動(dòng)拋出異常。
捕獲異常
如果只是拋出異常,而不捕獲異常的話,是比較危險(xiǎn)的。這意味著當(dāng)前任務(wù)立即終止,不再執(zhí)行(當(dāng)然,后續(xù)的其他任務(wù)會(huì)正常執(zhí)行)。此外,這個(gè)異常信息會(huì)層層往上,拋給上層的調(diào)用者。如果一直未被捕獲,則最終會(huì)拋給瀏覽器,瀏覽器控制臺(tái)就會(huì)報(bào)錯(cuò)。
接下來(lái),我們看一下不同代碼場(chǎng)景下的異常處理方案。
上報(bào)異常
如果有必要的話,你可以把異常信息和日志,上報(bào)給監(jiān)控服務(wù)器,然后集中分析。我每天上班第一件事,就是打開(kāi)監(jiān)控系統(tǒng),看錯(cuò)誤日志,然后對(duì)癥下藥解決問(wèn)題
同步代碼的異常處理
通過(guò) throw 拋出異常
我們可以通過(guò) throw關(guān)鍵字,拋出一個(gè)用戶自定義的異常。當(dāng)代碼執(zhí)行時(shí)遇到 throw 語(yǔ)句時(shí),當(dāng)前函數(shù)會(huì)停止停止,即:當(dāng)前函數(shù) throw 后面的代碼不會(huì)再執(zhí)行。
throw 意思是,告訴調(diào)用者,當(dāng)前被調(diào)用的函數(shù)報(bào)錯(cuò)了,調(diào)用者接下來(lái)需要捕獲異?;蛘咝薷拇a邏輯。
可以在 throw 的后面添加表達(dá)式或者數(shù)據(jù)類型,將添加的內(nèi)容拋出去。數(shù)據(jù)類型可以是:number、string、boolean、對(duì)象等。
代碼舉例:
function sum(num1, num2) {if (typeof num1 !== "number") {throw "type error: num1傳入的類型有問(wèn)題, 必須是number類型"}if (typeof num2 !== "number") {throw "type error: num2傳入的類型有問(wèn)題, 必須是number類型"}return num1 + num2
}sum('a', 'b');
打印結(jié)果:
當(dāng)然,我們還可以 throw一個(gè)封裝好的對(duì)象。比如:
class myError {constructor(errCode, errMsg) {this.errCode = errMsg;this.errMsg = errMsg;}
}function foo() {throw new myError(-1, 'not login');
}foo();
上面這種寫(xiě)法比較麻煩,一般不這么寫(xiě)。其實(shí),JS中已經(jīng)內(nèi)置了 Error 類,專門(mén)用于生成錯(cuò)誤信息。
Error 類
JS內(nèi)置的 Error 類非常好用。
代碼舉例:
function foo() {throw new Error('not login');
}foo();
打印結(jié)果:
上面的打印結(jié)果可以看到,通過(guò) Error 拋出來(lái)的錯(cuò)誤,不僅可以看到報(bào)錯(cuò)信息,還可以看到調(diào)用棧,便于快速定位問(wèn)題所在。非常方便。
通過(guò) try catch 捕獲異常
同步代碼,只拋出異常,不捕獲異常的代碼舉例:
function foo() {throw new Error('not login');
}foo();
// 當(dāng)前任務(wù)立即終止,不再執(zhí)行;下面這行代碼和 foo() 都在同一個(gè) 同步任務(wù) 中
console.log('qianguyihao');
打印結(jié)果:
可以看到,最后一行的 log 并沒(méi)有執(zhí)行。
我們可以使用 try catch 拋出異常, 對(duì)上述代碼進(jìn)行改進(jìn)。代碼舉例:
function foo() {throw new Error('not login');
}// 通過(guò) try catch 手動(dòng)捕獲異常
try {foo();
} catch (err) {console.log(err);
}// 當(dāng)前任務(wù)的后續(xù)代碼會(huì)繼續(xù)執(zhí)行
console.log('qianguyihao');
打印結(jié)果:
通過(guò) try catch finally 捕獲異常
如果有些代碼必須要執(zhí)行,我們可以放到 finally 里。
不管是否遇到異常,finally的代碼一定會(huì)執(zhí)行。
如果 try 和 finally 中都有返回值,那么會(huì)使用finally中的返回值。
代碼舉例:
function foo() {throw new Error('not login');
}// 通過(guò) try catch 捕獲異常
try {foo();
} catch (err) {console.log(err);
} finally {console.log("finally")
}// 后續(xù)代碼會(huì)繼續(xù)執(zhí)行
console.log('qianguyihao');
try catch 只能捕獲同步代碼的異常
try catch只能捕獲同步代碼里的異常,而 Promise.reject() 是異步代碼。
原因是:當(dāng)異步函數(shù)拋出異常時(shí),對(duì)于宏任務(wù)而言,執(zhí)行函數(shù)時(shí)已經(jīng)將該函數(shù)推入棧,此時(shí)并不在 try-catch 所在的棧,所以 try-catch 并不能捕獲到錯(cuò)誤。對(duì)于微任務(wù)而言(比如 promise)promise 的構(gòu)造函數(shù)的異常只能被自帶的 reject 也就是.catch 函數(shù)捕獲到。
使用 window.onerror 監(jiān)聽(tīng)未被捕獲的代碼異常
如果JS代碼拋出了異常但沒(méi)有進(jìn)行捕獲,我們可以使用 JS 自帶的 window.onerror 事件監(jiān)聽(tīng)到這些錯(cuò)誤。
代碼舉例:
// 監(jiān)聽(tīng)同步代碼的異常
window.onerror = (event) => {console.error('onerror 監(jiān)聽(tīng)到未被捕獲的異常:', event)
};function foo1() {throw new Error('not login');
}function foo2() {throw new Error('network error');
}foo1();
foo2();
打印結(jié)果:
Promise 的異常處理
reject() 會(huì)自動(dòng)拋出異常
在使用 Promise 時(shí),當(dāng)我們調(diào)用了 reject() 之后,系統(tǒng)會(huì)自動(dòng)拋出異常,不需要我們手動(dòng)拋出異常。這是 Promise的內(nèi)部機(jī)制。但是我們需要手動(dòng)捕獲異常。
當(dāng) Promise 進(jìn)入 rejected狀態(tài)之后,會(huì)觸發(fā) catch()方法的執(zhí)行,捕獲異常。此時(shí),成功完成了Promise異常處理的閉環(huán)。
在then() 中拋出異常(重要)
當(dāng)then()方法傳入的回調(diào)函數(shù)中,如果遇到異?;蛘呤謩?dòng)拋出異常,那么,then()所返回的新的 Promise 會(huì)進(jìn)入rejected 狀態(tài),進(jìn)而觸發(fā)新Promise 的 catch() 方法的執(zhí)行,做異常捕獲。
場(chǎng)景1:在then()方法傳入的回調(diào)函數(shù)中,如果代碼在執(zhí)行時(shí)遇到異常,系統(tǒng)會(huì)自動(dòng)拋出異常。此時(shí)我們需要在 catch() 里手動(dòng)捕獲異常,否則會(huì)報(bào)錯(cuò)。
自動(dòng)拋出異常的代碼舉例:(由于沒(méi)有捕獲異常,所以會(huì)報(bào)錯(cuò))
const myPromise = new Promise((resolve, reject) => {resolve('qianguyihao1 fulfilled');
});myPromise.then(res => {console.log('res1:', res);// 顯然,person 并沒(méi)有 forEach()方法。所以,代碼在執(zhí)行時(shí),會(huì)遇到異常。const person = { name: 'vae' };person.forEach(item => {console.log('item:', item);})// 這行代碼不會(huì)執(zhí)行,因?yàn)樯厦娴拇a報(bào)錯(cuò)了console.log('qianguyihao2');
}).then(res => {console.log('res2:', res);
})// 定時(shí)器里的代碼正常執(zhí)行
setTimeout(() => {console.log('qianguyihao3');
}, 100)
運(yùn)行結(jié)果:
代碼改進(jìn):(代碼在執(zhí)行時(shí)遇到異常,此時(shí)我們捕獲異常,所以系統(tǒng)不會(huì)報(bào)錯(cuò),這才是推薦的寫(xiě)法)
const myPromise = new Promise((resolve, reject) => {resolve('qianguyihao1 fulfilled');
});myPromise.then(res => {console.log('res1:', res);// 顯然,person 并沒(méi)有 forEach()方法。所以,代碼在執(zhí)行時(shí),會(huì)遇到異常。const person = { name: 'vae' };person.forEach(item => {console.log('item:', item);})// 這行代碼不會(huì)執(zhí)行,因?yàn)樯厦娴拇a報(bào)錯(cuò)了console.log('qianguyihao2');
}).then(res => {console.log('res2:', res);
}).catch(err => {// 在 catch()方法傳入的會(huì)調(diào)函數(shù)里,捕獲異常console.log('err2:', err);
})// 定時(shí)器里的代碼正常執(zhí)行
setTimeout(() => {console.log('qianguyihao3');
}, 100)
打印結(jié)果
場(chǎng)景2:在then()方法傳入的回調(diào)函數(shù)中,如果我們手動(dòng)拋出異常,此時(shí)我們需要在 catch() 里手動(dòng)捕獲異常,否則會(huì)報(bào)錯(cuò)。
代碼舉例:(手動(dòng)拋出異常,未捕獲,所以會(huì)報(bào)錯(cuò))
const myPromise = new Promise((resolve, reject) => {resolve('qianguyihao fulfilled 1');
});myPromise.then(res => {console.log('res1:', res);// 手動(dòng)拋出異常throw new Error('qianguyihao rejected 2')
}).then(res => {console.log('res2:', res);
})// 定時(shí)器里的代碼正常執(zhí)行
setTimeout(() => {console.log('qianguyihao3');
}, 100)
打印結(jié)果:
代碼改進(jìn):(代碼在執(zhí)行時(shí)遇到異常,此時(shí)我們捕獲異常,所以系統(tǒng)不會(huì)報(bào)錯(cuò),這才是推薦的寫(xiě)法)
const myPromise = new Promise((resolve, reject) => {resolve('qianguyihao fulfilled 1');
});myPromise.then(res => {console.log('res1:', res);// 手動(dòng)拋出異常throw new Error('qianguyihao rejected 2')
}).then(res => {console.log('res2:', res);
}, (err) => {console.log('err2:', err);
})// 定時(shí)器里的代碼正常執(zhí)行
setTimeout(() => {console.log('qianguyihao3');
}, 100)
打印結(jié)果:
使用 unhandledrejection 事件監(jiān)聽(tīng)未被捕獲的Promise異常
如果Promise拋出了異常但沒(méi)有進(jìn)行捕獲,我們可以使用JS自帶的 unhandledrejection 事件監(jiān)聽(tīng)到這些錯(cuò)誤。這個(gè)事件非常有用,尤其是當(dāng)我們需要集中做日志收集時(shí),屢試不爽。這個(gè)事件只能用于監(jiān)聽(tīng) Promise 中的異常,不能用于其他同步代碼的異常。
先來(lái)看下面這行代碼:
const myPromise = new Promise((resolve, reject) => {console.log('qianguyihao1');reject('not login');console.log('qianguyihao2');
})
打印結(jié)果:
上面的代碼拋出了異常,但沒(méi)有捕獲異常,所以我們可以用 unhandledrejection 事件監(jiān)聽(tīng)到。代碼舉例:
// 監(jiān)聽(tīng)未被捕獲的 Promise 異常
window.addEventListener('unhandledrejection', (event) => {console.error(`unhandledrejection 監(jiān)聽(tīng)到異常,寫(xiě)法1: ${event.reason}`)
});window.onunhandledrejection = event => {console.error(`unhandledrejection 監(jiān)聽(tīng)到異常,寫(xiě)法2: ${event.reason}`);
};window.onerror = (event) => {console.error('onerror 監(jiān)聽(tīng)到異常:', event);
};const promise1 = new Promise((resolve, reject) => {reject('not login');
})const promise2 = new Promise((resolve, reject) => {throw new Error('network error');resolve();
})
打印結(jié)果:
可以看到,promise1 和 Promise2 的異常,都被 unhandledrejection 事件收集到了。
代碼舉例2:
window.addEventListener('unhandledrejection', (event) => {console.error(`unhandledrejection 監(jiān)聽(tīng)到異常: ${event.reason}`)
});window.onerror = (event) => {console.error('onerror 監(jiān)聽(tīng)到異常:', event);
};const myPromise = new Promise((resolve, reject) => {setTimeout(() => {throw new Error('not login');resolve();}, 100);
})
打印結(jié)果:
上面的代碼中,unhandledrejection 無(wú)法監(jiān)聽(tīng)異常,因?yàn)槎〞r(shí)器里的代碼屬于宏任務(wù)。
resolve()之后,再報(bào)錯(cuò)無(wú)效
代碼舉例:
const myPromise = new Promise((resolve, reject) => {resolve('fulfilled');throw new Error("自定義錯(cuò)誤");
});myPromise.then(res => {console.log("res", res);return res + 1;
}).catch(err => {console.log("err:", err);
});
打印結(jié)果:
res fulfilled
上方代碼中,第3行的異常代碼相當(dāng)于沒(méi)寫(xiě)。因?yàn)?resolve()之后,Promise的狀態(tài)會(huì)立即進(jìn)入 fulfilled,然后走到 then(),狀態(tài)不可逆。
async await 的異常處理
捕獲異常
代碼舉例:
function requestData1() {return new Promise((resolve, reject) => {reject('任務(wù)1失敗');})
}function requestData2() {return new Promise((resolve, reject) => {resolve('任務(wù)2成功');})
}async function getData() {// requestData1 在執(zhí)行時(shí),遇到異常await requestData1();/*由于上面的代碼在執(zhí)行是遇到異常,所以,這里雖然什么都沒(méi)寫(xiě),底層默認(rèn)寫(xiě)了如下代碼:return Promise.reject('任務(wù)1失敗');*/// 下面這兩行代碼不會(huì)再執(zhí)行了,因?yàn)樯厦娴拇a遇到了異常console.log('qianguyihao1');await requestData2();
}getData();// 【注意】定時(shí)器里的代碼會(huì)正常實(shí)行,因?yàn)樗诹硗庖粋€(gè)宏任務(wù)里,不在上面的微任務(wù)里
setTimeout(() => {console.log('qianguyihao2');
}, 100)
打印結(jié)果:
所以,為了避免上述問(wèn)題,我們還需要手動(dòng)捕獲異常。我們捕獲到異常之后,這個(gè)異常就不會(huì)繼續(xù)網(wǎng)上拋了,更不會(huì)拋給瀏覽器。
感謝閱讀,覺(jué)得有幫助可以點(diǎn)點(diǎn)關(guān)注點(diǎn)點(diǎn)贊,謝謝!