溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

淺談如何優(yōu)雅處理JavaScript異步錯(cuò)誤

發(fā)布時(shí)間:2020-09-03 15:02:17 來源:腳本之家 閱讀:222 作者:XXXSpade 欄目:web開發(fā)

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里面就萬事大吉了嗎?不太準(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。可以使用類似if(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ò)誤。

淺談如何優(yōu)雅處理JavaScript異步錯(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é)

  1. 通常異??赡苁穷A(yù)期的或者超出預(yù)期的,不管怎樣,使用try/catch沒有問題。
  2. 對(duì)于超出預(yù)期的錯(cuò)誤,盡量使用catch來保證它們會(huì)被捕獲到。
  3. 把錯(cuò)誤處理器添加到window對(duì)象上,它會(huì)捕獲到異步錯(cuò)誤,符合了DRY和SOLID原則。一個(gè)全局的錯(cuò)誤處理器可以幫你保持異步代碼整潔。

Reference

async-await-error-handling
nodejs-v12-lts

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI