如何處理nodejs中的異常?

如何處理nodejs中的異常?nodejs異常導致程序崩潰,這個之前困擾我許久的問題終於得到了解決,就好像我心中的疙瘩解開了,於是我迫不及待地想記錄一下我總結的方法。

異常處理是程序運行中必須要關注的地方,當異常出現後,應該第一時間關注到,並且快速解決。大部分程序員們都不敢保證自己的代碼百分比正確,所以應該在寫代碼時就要對異常提前做預防處理,儘量保證在異常出現時,給用戶一個友好的提示,不至於服務掛起導致請求超時,並且能將異常信息做記錄上報,方便後期排查解決。

nodejs 是單線程的,當程序中產生未處理的異常時,會導致程序的崩潰,我總結了網上幾種處理方法:

在nodejs中的異常捕獲

1.使用uncaughtException

我們可以uncaughtException來全局捕獲未捕獲的Error,同時你還可以將此函數的調用棧打印出來,捕獲之後可以有效防止node進程退出,如:

process.on('uncaughtException', function (err) {
  //打印出錯誤
  console.log(err);
  //打印出錯誤的調用棧方便調試
  console.log(err.stack);
});

這相當於在node進程內部進行守護, 但這種方法很多人都是不提倡的,說明你還不能完全掌控Node.JS的異常。

2.使用 try/catch

我們可以在回調前加try/catch,以確保線程的安全。

var http = require('http');

http.createServer(function(req, res) {
  try {
    handler(req, res);
  } catch(e) {
    console.log('\r\n', e, '\r\n', e.stack);
    try {
      res.end(e.stack);
    } catch(e) { }
  }
}).listen(8080, '127.0.0.1');

console.log('Server running at http://127.0.0.1:8080/');

var handler = function (req, res) {
  //Error Popuped
  var name = req.params.name;

  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello ' + name);
};

這種方案的好處是,可以將錯誤和調用棧直接輸出到當前發生的網頁上。

在express官方文檔中有幾種推薦的方法:

1.捕捉錯誤

確保Express能夠捕獲運行路由處理程序和中間件時發生的所有錯誤,這一點很重要。

路由處理程序和中間件內部的同步代碼中發生的錯誤不需要任何額外的工作。如果同步代碼引發錯誤,則Express將捕獲並處理該錯誤。例如:

app.get('/', function (req, res) {
  throw new Error('BROKEN') // Express will catch this on its own.
})

對於由路由處理程序和中間件調用的異步函數返回的錯誤,必須將它們傳遞給next()Express捕獲並處理它們的函數。例如:

app.get('/', function (req, res, next) {
  fs.readFile('/file-does-not-exist', function (err, data) {
    if (err) {
      next(err) // Pass errors to Express.
    } else {
      res.send(data)
    }
  })
})

如果將任何內容傳遞給該next()函數(字符串除外’route’),Express都會將當前請求視爲錯誤,並且將跳過所有剩餘的非錯誤處理路由和中間件函數。

如果序列中的回調不提供數據,僅提供錯誤,則可以按以下方式簡化此代碼:

app.get('/', [
  function (req, res, next) {
    fs.writeFile('/inaccessible-path', 'data', next)
  },
  function (req, res) {
    res.send('OK')
  }
])

在以上示例next中,提供了作爲的回調fs.writeFile,無論有無錯誤都將調用該回調。如果沒有錯誤,則執行第二個處理程序,否則Express捕獲並處理該錯誤。

您必須捕獲由路由處理程序或中間件調用的異步代碼中發生的錯誤,並將它們傳遞給Express進行處理。例如:

app.get('/', function (req, res, next) {
  setTimeout(function () {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})

上面的示例使用一個try…catch塊來捕獲異步代碼中的錯誤,並將它們傳遞給Express。如果try…catch 省略了該塊,則Express將不會捕獲該錯誤,因爲它不是同步處理程序代碼的一部分。

使用Promise可以避免try…catch塊的開銷,或者在使用返回Promise的函數時。例如:

app.get('/', function (req, res, next) {
  Promise.resolve().then(function () {
    throw new Error('BROKEN')
  }).catch(next) // Errors will be passed to Express.
})

由於promise會自動捕獲同步錯誤和已拒絕的promise,因此您可以簡單地提供next作爲最終捕獲處理程序,而Express將捕獲錯誤,因爲捕獲處理程序將錯誤作爲第一個參數。

您還可以使用一系列處理程序來依靠同步錯誤捕獲,方法是將異步代碼減少到一些瑣碎的事情。例如:

app.get('/', [
  function (req, res, next) {
    fs.readFile('/maybe-valid-file', 'utf-8', function (err, data) {
      res.locals.data = data
      next(err)
    })
  },
  function (req, res) {
    res.locals.data = res.locals.data.split(',')[1]
    res.send(res.locals.data)
  }
])

上面的示例從readFile 調用中獲得了一些簡單的語句。如果readFile導致錯誤,則將錯誤傳遞給Express,否則您將快速回到鏈中下一個處理程序中的同步錯誤處理領域。然後,上面的示例嘗試處理數據。如果失敗,則同步錯誤處理程序將捕獲該錯誤。如果您已在readFile回調中完成此處理,則應用程序可能會退出並且Express錯誤處理程序將無法運行。

無論使用哪種方法,如果都希望調用Express錯誤處理程序並使應用程序繼續存在,則必須確保Express收到錯誤。

2.默認錯誤處理程序

function errorHandler (err, req, res, next) {
  if (res.headersSent) {
    return next(err)
  }
  res.status(500)
  res.render('error', { error: err })
} 

3.編寫錯誤處理程序

定義錯誤處理中間件功能的方式與其他中間件功能相同,只是錯誤處理功能具有四個參數而不是三個參數 (err, req, res, next)。例如:

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

您最後定義錯誤處理中間件,之後再定義app.use()並路由調用;例如:

var bodyParser = require('body-parser')
var methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(function (err, req, res, next) {
  // logic
})

來自中間件功能的響應可以是任何格式,例如HTML錯誤頁面,簡單消息或JSON字符串。

出於組織(和更高級別的框架)的目的,您可以定義多個錯誤處理中間件功能,就像使用常規中間件功能一樣。例如,爲使用XHR和不使用的請求定義錯誤處理程序:

var bodyParser = require('body-parser')
var methodOverride = require('method-override')

app.use(bodyParser.urlencoded({
  extended: true
}))
app.use(bodyParser.json())
app.use(methodOverride())
app.use(logErrors)
app.use(clientErrorHandler)
app.use(errorHandler)

在此示例中,泛型logErrors可能會將請求和錯誤信息寫入stderr,例如:

function logErrors (err, req, res, next) {
  console.error(err.stack)
  next(err)
}

同樣在此示例中,clientErrorHandler定義如下:在這種情況下,錯誤將明確傳遞給下一個錯誤。

請注意,在錯誤處理函數中不調用“ next”時,您負責編寫(並結束)響應。否則,這些請求將“掛起”,並且不符合垃圾回收的條件。

function clientErrorHandler (err, req, res, next) {
  if (req.xhr) {
    res.status(500).send({ error: 'Something failed!' })
  } else {
    next(err)
  }
}

但是當我按照官方文檔全局添加了錯誤處理,在下載文件的時候還是會有異常沒有處理。場景:通過a標籤的download屬性下載文件,如果文件不存在,在chrome瀏覽器發送的報文可以捕獲到異常,但是在firefox瀏覽器中express捕獲不到異常導致程序崩潰,報錯代碼如下:

{ NotFoundError: Not Found
    at /xxx/app.js
    ...}
GET /1AB376350001_1.PNG 404 3.754 ms - 274

這是因爲在chrome和firefox中下載文件請求頭的content-type格式不一致,需要分類處理
(來自中間件功能的響應可以是任何格式,例如HTML錯誤頁面,簡單消息或JSON字符串)。

// when status is 404, error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  if( 404 === err.status  ){
      res.format({
          'text/plain': () => {
              res.send({message: 'not found Data'});
          },
          'text/html': () => {
              res.render('404.html');
          },
          'application/json': () => {
              res.send({message: 'not found Data'});
          },
          'default': () => {
              res.status(406).send('Not Acceptable');
          }
      })
  }

  // when status is 500, error handler
  if(500 === err.status) {
      return res.send({message: 'error occur'});
  }
});

總結:

  1. 局部捕捉異常,需要捕獲運行路由處理程序和中間件時發生的所有錯誤

  2. 在全局處理異常時,最好還是對異常進行分類(例如HTML錯誤頁面,簡單消息或JSON字符串)

如有疏漏,請不吝指正,後續會繼續更新,敬請期待。。。

參考文檔:

  1. https://www.expressjs.com.cn/4x/api.html

  2. https://www.cnblogs.com/cnxkey/articles/7442203.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章