Node.js 跟 JavaScript同樣,同步代碼中的異常咱們能夠經過 try catch 來捕獲.html
但異步代碼呢? 咱們來看一個 http server 啓動的代碼,這個也是個典型的異步代碼。web
const http = require('http') try { const server = http.createServer(function (req, res) { console.log('來了') throw new Error('hi') res.end('helo') }) server.listen(3002) } catch (err) { console.log('出錯了') }
咱們發現異步代碼的異常沒法直接捕獲。這會致使 Node.js 進程退出。最明顯的就是 web server 直接掛掉了。promise
異步代碼也有解決辦法,咱們直接把try catch 寫在異步代碼的回調裏面:瀏覽器
const http = require('http') try { const server = http.createServer(function (req, res) { try { throw new Error('hi') } catch (err) { console.log('出錯了') } res.end('helo') }) server.listen(3002) } catch (err) { console.log('出錯了') }
這樣也能catch到錯誤。網絡
然而業務代碼很是複雜,並非全部的狀況咱們都能預料到。好比在try...catch以後又出現一個throw Error. app
全部沒有catch 的Error都會往上冒泡直到變成一個全局的 uncaughtException。 Node.js裏對未捕獲的異常會檢查有沒有監聽該事件,若是沒有就把進程退出:異步
function _MyFatalException(err){ if(!process.emit('uncaughtException',err)){ console.error(err.stack); process.emit('exit',1); } }
所以,防止異步回調異常致使進程退出的辦法彷彿就是監聽該事件函數
process.on('uncaughtException', function(err) { console.log('出錯了,我記錄你,並吃掉你') }) const http = require('http') try { const server = http.createServer(function (req, res) { try { throw new Error('hi') } catch (err) { console.log('出錯了') } throw new Error('有一個error') res.end('helo') }) server.listen(3002) } catch (err) { console.log('出錯了') }
這樣進程不會退出。但 極其不優雅
。 由於 uncaughtException 中沒有了req和res上下文,沒法友好響應用戶。另外可能形成內存泄漏(具體參考網絡其餘資料)ui
所以,uncaughtException 適合用來作Node.js 整個應用最後的兜底。(記錄日誌or重啓服務)this
若是使用了promise,且非異步reject了。在 Node.js 中,這個promise reject 行爲會在控制檯打印,但目前Node版本不會形成進程退出,也不會觸發全局 uncaughtException.
promise最有爭議的地方就是當一個promise失敗可是沒有rejection handler處理錯誤時靜默失敗。不過瀏覽器和Node.js都有相應的處理機制,二者大同小異,都是經過事件的方式監聽. 有兩個全局事件能夠用來監聽 Promise 異常:
最好的處理方式,就是應該感知到本身業務代碼中的異常。這樣的話,不管業務開發人員本身處理了仍是沒處理,都能在應用上層catch到進行日誌記錄。 更佳的狀況是:在感知到錯誤後,能給瀏覽器一些默認的提示。
但是業務代碼裏有同步有異步,如此複雜的代碼如何能所有cover住呢?
這個會有一些技巧:好比假設咱們的業務代碼所有被包裹在本身的一個Promise中,且業務代碼的每個異步函數均可以被咱們注入catch回調。在這樣完美的狀況下,咱們就能在最外層捕獲內部發生的全部異常了。
Koa 就是這麼幹的。Koa1 用 co來運行中間件,co就能夠把generator運行起來且捕獲其中的異步錯誤。想了解具體原理的,可能要去看更詳細的資料了
這種方式任何JavaScript程序均可以使用,是業務開發人員本身要作的。很少說了
因爲Koa是洋蔥模型,所以能夠在業務邏輯的前置中間件裏捕獲後面中間件的錯誤。這裏是基於 yield 異步異常能夠被try catch的機制。例如:
app.use(function *(next) { try { yield next; } catch (err) { console.log('哇哈 抓到一個錯誤') // 友好顯示給瀏覽器 this.status = err.status || 500; this.body = err.message; this.app.emit('error', err, this); } });
實際上,上述中間件的工做 ctx.onerror 已經作了。 Koa 內核會自動把中間件的錯誤交給 ctx.onerror 處理,所以這個中間件我感受不必寫了(除非要自定義這個默認的錯誤處理邏輯)。
若是全部中間件都沒有捕獲到某個異常,那麼co會捕獲到。co會調用context對象的onerror, 從而作一些處理(例如返回給瀏覽器500錯誤),同時觸發 app.onerror
所以,在app.onerror裏,你能夠作些日誌記錄或自定義響應
若是 Koa 都沒有捕獲到異常,那麼就由Node來兜底了。不過這個通常不會發生,除非你在app.onerror裏還要扔出異常(然而這是個promise異常,也不會觸發uncaughtException)。
在 Koa1 中間件裏,你可使用 this.throw(status, msg)
拋出異常。 Koa的底層其實本質上會使用 http-errors模塊包裝這個Error, 並直接 throw這個異常。
如下是 this.throw 函數源碼:
// 將你傳遞的錯誤碼和msg包裝爲一個 Error對象 throw: function(){ throw createError.apply(null, arguments); }
其中 createError函數至關於:
var err = new Error(msg); err.status = status; throw err; // 包裝後再拋出,ctx.onerror才能正確響應錯誤碼給瀏覽器,不然都是500
所以 中間件 中你調用 this.throw 函數實際上就是真的 throw了一個異常,最終會致使 co 異常。
因爲前文講到的 Koa co 錯誤捕獲機制(co-->catch-->ctx.onerror-->app.onerror),所以,你在任何中間件中throw的異常均可以被app.onerror捕獲到。
co在運行generator時,若是某個yield右側又是一個generator,那麼co也會遞歸地去運行它。固然也會捕獲這個嵌套的異步異常。但有些狀況下嵌套異步會逃出一個異步的錯誤檢測機制。
好比在Promise裏作了另一個異步操做, 在另外的異步操做裏拋出了異常。
var fn = function () { return new Promise (function (resolve, reject) { setTimeout(function(){throw new Error('inner bad')}) }) }
這個異常,Promise就沒法catch到。 一樣,在generator裏若是用了這樣的方式,異常也會逃逸緻使沒法捕獲。
問題:逃逸出Koa 的 co異步調用鏈的代碼,會致使co沒法catch異常。
不如去看看egg怎麼作的吧