Koa入門教程[3]-錯誤和異常處理

Node.js 中的異常

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異常

若是使用了promise,且非異步reject了。在 Node.js 中,這個promise reject 行爲會在控制檯打印,但目前Node版本不會形成進程退出,也不會觸發全局 uncaughtException.

promise最有爭議的地方就是當一個promise失敗可是沒有rejection handler處理錯誤時靜默失敗。不過瀏覽器和Node.js都有相應的處理機制,二者大同小異,都是經過事件的方式監聽. 有兩個全局事件能夠用來監聽 Promise 異常:
  • unhandledRejection:當promise失敗(rejected),但又沒有處理時觸發,event handler 有2個參數: reason,promise;
  • rejectionHandled: 當promise失敗(rejected),被處理時觸發,hanler 有1個參數: promise;

到底該如何處理異常

最好的處理方式,就是應該感知到本身業務代碼中的異常。這樣的話,不管業務開發人員本身處理了仍是沒處理,都能在應用上層catch到進行日誌記錄。 更佳的狀況是:在感知到錯誤後,能給瀏覽器一些默認的提示。

但是業務代碼裏有同步有異步,如此複雜的代碼如何能所有cover住呢?

這個會有一些技巧:好比假設咱們的業務代碼所有被包裹在本身的一個Promise中,且業務代碼的每個異步函數均可以被咱們注入catch回調。在這樣完美的狀況下,咱們就能在最外層捕獲內部發生的全部異常了。

Koa 就是這麼幹的。Koa1 用 co來運行中間件,co就能夠把generator運行起來且捕獲其中的異步錯誤。想了解具體原理的,可能要去看更詳細的資料

Koa 中捕獲異常和錯誤的機制

  • 業務本身try catch

這種方式任何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 處理,所以這個中間件我感受不必寫了(除非要自定義這個默認的錯誤處理邏輯)。

  • 監聽app.on('error')

若是全部中間件都沒有捕獲到某個異常,那麼co會捕獲到。co會調用context對象的onerror, 從而作一些處理(例如返回給瀏覽器500錯誤),同時觸發 app.onerror

所以,在app.onerror裏,你能夠作些日誌記錄或自定義響應

  • uncaughtException

若是 Koa 都沒有捕獲到異常,那麼就由Node來兜底了。不過這個通常不會發生,除非你在app.onerror裏還要扔出異常(然而這是個promise異常,也不會觸發uncaughtException)。

Koa錯誤處理最佳實踐

  • 拋出異常

在 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怎麼作的吧

相關文章
相關標籤/搜索