淺談如何優(yōu)雅處理JavaScript異步錯(cuò)誤
1. try/catch
try/catch基本上是大家最常和async/await一起使用的,基本上我們會(huì)用它去包圍大部分的異步方法。await關(guān)鍵字后面的promise一旦reject了,就會(huì)拋出一個(gè)異常錯(cuò)誤。
run(); async function run() { try { await Promise.ject(new Error('Oops!')); } catch (err) { console.error(error.message); } }
try/catch同樣也可以處理同步的錯(cuò)誤,比如下面:
async function run() { const v = null; try { await Promise.resolve('foo'); v.thisWillThrow; } catch (error) { // 會(huì)出現(xiàn)"TypeError: Cannot read property 'thisWillThrow' of null" console.error(error.message); } }
好像我們只要無腦把邏輯都放到try/catch里面就萬(wàn)事大吉了嗎?不太準(zhǔn)確,下面的代碼卻會(huì)導(dǎo)致unhandled promise rejection。這個(gè)return關(guān)鍵字直接返回就錯(cuò)誤卻不會(huì)被捕獲:
async function run() { try { // 直接返回Promise,而不是用await關(guān)鍵字 return Promise.reject(new Error('Oops!')); } catch (error) { console.error(error.message); } }
一種處理方式是使用return await來解決。
try catch捕獲不了回調(diào)函數(shù)。try catch 僅僅在單一執(zhí)行環(huán)境中奏效。是在回調(diào)中加入try catch 來捕獲錯(cuò)誤。
setTimeout(funciton() { try { fn() } catch (e) { // handle error } })
這是奏效的。 不過try catch會(huì)在各個(gè)地方。 V8引擎是不鼓勵(lì)try catch在函數(shù)中的使用的。 之前把try catch移到頂層來捕獲調(diào)用棧的錯(cuò)誤,但這個(gè)對(duì)異步代碼不會(huì)奏效。
2. Golang-style(then)
golang style即使用.then()的方法來將一個(gè)promise轉(zhuǎn)換為另一個(gè)處理完錯(cuò)誤的reject promise??梢允褂妙愃苅f(err)來進(jìn)行檢查:
async function throwAnError() { throw new Error('Opps!'); } async function runAwait() { let err = await throwAnError(); if (err){ console.error(err.message); } }
這么寫會(huì)直接拋出異常,因?yàn)檫@個(gè)方法拋出了異常,但是該方法本身沒有用try/catch捕獲。很多時(shí)候,我們?cè)谑褂玫谌綆?kù)的時(shí)候可能會(huì)出現(xiàn)這種情況。
then()解決方法
async function runAwait() { let err = await throwAnError().then(() => null, err => err); if (err){ console.error(err.message); } }
then()的方式,就會(huì)等待promise狀態(tài)resolve或reject后然后執(zhí)行相應(yīng)的回調(diào),然后判斷err對(duì)象并處理,所以其實(shí)它相當(dāng)于被捕獲了。
同時(shí)返回錯(cuò)誤和值
async function run() { let [err, res] = await throwAnError().then(v => [null, v], err => [err, null]); if (err){ console.error(err.message); } console.log(res) }
結(jié)果:這么做可以通過解構(gòu)返回一個(gè)數(shù)組,包含了結(jié)果和error對(duì)象。當(dāng)然如果是reject就會(huì)返回null和error對(duì)象;而如果resolved返回?cái)?shù)組的第一個(gè)error對(duì)象就為null,第二個(gè)就是結(jié)果。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):這種模式可以更簡(jiǎn)潔地處理,同時(shí)可以不需要寫catch。
缺點(diǎn)1:這是非常重復(fù)性的,每次執(zhí)行異步操作都需要去判斷error對(duì)象。
缺點(diǎn)2:無法幫助處理run方法中的同步錯(cuò)誤。
所以這種方式需要謹(jǐn)慎使用。
3. Catch捕獲
上面兩種模式都可以處理異步錯(cuò)誤,但是對(duì)于錯(cuò)誤處理,最好的情況是在異步邏輯的最后加上catch,這樣可以保證所有錯(cuò)誤都被捕獲到。其實(shí)這也是一個(gè)原則,即統(tǒng)一處理錯(cuò)誤,而不是單獨(dú)去處理每個(gè)錯(cuò)誤。
async function run() { return Promise.reject(new Error('Oops!')); } run().catch(function handleError(err) { console.error(err.message); }).catch( err => { process.nextTick(() => { throw errl}); })
使用catch捕獲錯(cuò)誤,如果handleError本身也有錯(cuò)誤,就需要再catch一遍,但是為了避免回調(diào)地獄,如果該方法發(fā)生了錯(cuò)誤就終止該進(jìn)程。
優(yōu)缺點(diǎn)
- 使用catch的話,不管異步方法本身是否捕獲錯(cuò)誤,它都會(huì)去捕獲異步錯(cuò)誤。
- 使用try/catch無法避免catch本身拋出異常,而如果它拋出了那除了嵌套多一層try/catch外,最好的做法就是加catch來讓代碼更簡(jiǎn)潔。
4 全局錯(cuò)誤捕獲
4.1 瀏覽器全局錯(cuò)誤捕獲
瀏覽器全局處理基本上就是依靠事件,因?yàn)闉g覽器是事件驅(qū)動(dòng)的。一旦拋出錯(cuò)誤,解釋器在執(zhí)行環(huán)境上下文中停止執(zhí)行并展開,此時(shí)會(huì)有一個(gè)onerror全局事件拋出:
window.addEventListener('error', function (e) { var error = e.error; console.log(error); })
全局錯(cuò)誤處理器會(huì)捕獲任何在執(zhí)行環(huán)境中發(fā)生的錯(cuò)誤,即便是不同的對(duì)象發(fā)生的錯(cuò)誤事件,或者是各種類型的錯(cuò)誤。這是全局集中處理錯(cuò)誤的一種常見方式。
調(diào)用棧
調(diào)用棧在定位問題的時(shí)候十分重要,我們可以使用調(diào)用棧在處理器中處理特定的錯(cuò)誤。
window.addEventListener('error', function (e) { var stack = e.error.stack; var message = e.error.toString(); if (stack) { message += '\n' + stack; } var xhr = new XMLHttpRequest(); xhr.open('POST', '/log', true); // Fire an Ajax request with error details xhr.send(message); });
通過日志可以看到,具體什么情況觸發(fā)了什么錯(cuò)誤。在調(diào)試時(shí)調(diào)用堆棧也會(huì)非常有用。你 可以分析log,看到什么條件下觸發(fā)了錯(cuò)誤。
注意:
如果跨域腳本是不會(huì)看到錯(cuò)誤的。 在JS中,錯(cuò)誤信息僅僅是允許在同一個(gè)域中。
個(gè)人想法
更多的時(shí)候,代碼拋出了異常,我們更關(guān)注的是在運(yùn)行時(shí),某個(gè)變量的值是什么,是否這個(gè)變量的值導(dǎo)致了錯(cuò)誤,所以打印出調(diào)用時(shí)的跟多的信息更重要。
4.2 Node.js全局錯(cuò)誤捕獲
Node.js本身的異常處理要復(fù)雜得多,因?yàn)樯婕暗搅诉M(jìn)程或線程拋出異常的問題。
基于Koa的全局錯(cuò)誤處理
nodejs是error-first的異步處理機(jī)制,此處底層會(huì)調(diào)用net模塊的listen方法并在錯(cuò)誤發(fā)生時(shí)執(zhí)行回調(diào)。
app.listen(app.config.listenPort, (err) => { if (err) throw err app.logger.info(`> Ready on http://localhost:${app.config.listenPort}`) })
路由錯(cuò)誤處理
對(duì)于每個(gè)路由,它可能也會(huì)有不同的錯(cuò)誤處理邏輯,這時(shí)路由進(jìn)來的請(qǐng)求就需要根據(jù)情況返回不同的異常碼和信息。
router.get('/loginAuth', async (ctx, next) => { try { const code = query.code const res = await requestToken(code) if (res.data.code !== 0) { ctx.app.logger.error(`request token error.Code is ${res.data.code} || response is: ${JSON.stringify(res.data.data)} || msg: ${res.data.message}`) ctx.body = { code: 10000, message: `request token by code error` } } else { ctx.body = res.data } } catch (err) { ctx.app.logger.error(`request api has exception ${ctx.request.url} || ${err.code} || ${err.message} || ${err.stack}`) ctx.body = { code: 500, message: `Error response` } } })
5. 總結(jié)
- 通常異??赡苁穷A(yù)期的或者超出預(yù)期的,不管怎樣,使用try/catch沒有問題。
- 對(duì)于超出預(yù)期的錯(cuò)誤,盡量使用catch來保證它們會(huì)被捕獲到。
- 把錯(cuò)誤處理器添加到window對(duì)象上,它會(huì)捕獲到異步錯(cuò)誤,符合了DRY和SOLID原則。一個(gè)全局的錯(cuò)誤處理器可以幫你保持異步代碼整潔。
Reference
async-await-error-handling
nodejs-v12-lts
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
防止按鈕在短時(shí)間內(nèi)被多次點(diǎn)擊的方法
一個(gè)按鈕可以在短時(shí)間內(nèi)多次點(diǎn)擊,那么有可能會(huì)被用戶惡意點(diǎn)擊,下面有個(gè)不錯(cuò)的方法可以有效制止,希望對(duì)大家有所幫助2014-03-03JavaScript數(shù)據(jù)類型和變量_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)類型和變量的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06前端實(shí)現(xiàn)讀取word文件并將其原樣式展示的幾種方案
在前端直接讀取并原樣展示W(wǎng)ord文檔是一個(gè)相對(duì)復(fù)雜的任務(wù),因?yàn)閃ord文檔的格式(如.doc或.docx)與Web技術(shù)棧使用的格式(HTML、CSS)不兼容,這篇文章主要給大家介紹了關(guān)于前端實(shí)現(xiàn)讀取word文件并將其原樣式展示的幾種方案,需要的朋友可以參考下2024-08-08詳解webpack4多入口、多頁(yè)面項(xiàng)目構(gòu)建案例
這篇文章主要介紹了詳解webpack4多入口、多頁(yè)面項(xiàng)目構(gòu)建案例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05bootstrap Validator 模態(tài)框、jsp、表單驗(yàn)證 Ajax提交功能
這篇文章主要介紹了bootstrap Validator 模態(tài)框、jsp、表單驗(yàn)證 Ajax提交功能的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-02-02JavaScript中檢測(cè)數(shù)據(jù)類型的四種方法
這篇文章主要給大家分享的是JavaScript中檢測(cè)數(shù)據(jù)類型的四種方法,有 typeof、instanceof、constructor、Object.prototype.toString.call(),下面文章詳細(xì)介紹內(nèi)容,需要的小伙伴可以參考一下2022-01-01