淺談前端錯誤處理

用戶反饋打開的頁面白屏幕,怎麼定位到產生錯誤的緣由呢?平常某次發佈怎麼肯定發佈會沒有引入bug呢?此時捕獲到代碼運行的bug並上報是多麼的重要。javascript

既然捕獲錯誤並上報是平常開發中不可缺乏的一環,那怎麼捕獲到錯誤呢?萬能的try...catchhtml

try{

throw new Error()

} catch(e) {

// handle error

}
複製代碼

看上去錯誤捕獲是多麼的簡單,然而下面的場景下就不能捕獲到了前端

try {

setTimeout(() => {

throw new Error('error')

})

} catch (e) {

// handle error

}
複製代碼

你會發現上面的例子中的錯誤不能正常捕獲,看來錯誤捕獲並非這樣簡單**try...catch**就能搞定,固然你也能夠爲異步函數包裹一層**try...catch**來處理。java

瀏覽器中,**window.onerror**來捕獲你的錯誤node

window.onerror = function (msg, url, row, col, error) {

console.log('error');

console.log({

msg, url, row, col, error

})

};
複製代碼

捕獲到錯誤後就能夠將錯誤上報,上報方式很簡單,你能夠經過建立簡單的**img**,經過**src**指定上報的地址,固然爲了不上報發送過多的請求,能夠對上報進行合併,合併上報。能夠定時將數據進行上報到服務端。git

但但你去看錯誤上報的信息的時候,你會發現一些這樣的錯誤**Script error**github

由於瀏覽器的同源策略,對於不一樣域名的錯誤,都拋出了**Script error**,怎麼解決這個問題呢?特別是如今基本上js資源都會放在cdn上面。web

解決方案數據庫

1:全部的資源都放在同一個域名下。可是這樣也會存在問題是不能利用cdn的優點。後端

2:增長跨域資源支持,在cdn 上增長支持主域的跨域請求支持,在script 標籤加**crossorigin**屬性

在使用Promise過程當中,若是你沒有catch,那麼能夠這樣來捕獲錯誤

window.addEventListener("unhandledrejection", function(err, promise) { 
    // handle error here, for example log 
});
複製代碼

如何在NodeJs中捕獲錯誤

NodeJs中的錯誤捕獲很重要,由於處理不當可能致使服務雪崩而不可用。固然了不只僅知道如何捕獲錯誤,更應該知道如何避免某些錯誤。

  • 當你寫一個函數的時候,你也許曾經思考過當函數執行的時候出現錯誤的時候,我是應該直接拋出throw,仍是使用callback或者event emitter仍是其它方式分發錯誤呢?

  • 我是否應該檢查參數是不是正確的類型,是否是null

  • 若是參數不符合的時候,你怎麼辦呢?拋出錯誤仍是經過callback等方式分發錯誤呢?

  • 若是保存足夠的錯誤來複原錯誤現場呢?

  • 若是去捕獲一些異常錯誤呢?try...catch仍是domain

操做錯誤VS編碼錯誤

1. 操做錯誤

操做錯誤每每發生在運行時,並不是因爲代碼bug致使,多是因爲你的系統內存用完了或者是因爲文件句柄用完了,也多是沒有網絡了等等

2.編碼錯誤

編碼錯誤那就比較容易理解了,多是undefined卻看成函數調用,或者返回了不正確的數據類型,或者內存泄露等等

處理操做錯誤

  • 你能夠記錄一下錯誤,而後什麼都不作

  • 你也能夠重試,好比由於連接數據庫失敗了,可是重試須要限制次數

  • 你也能夠將錯誤告訴前端,稍後再試

  • 也許你也能夠直接處理,好比某個路徑不存在,則建立該路徑

處理編碼錯誤

錯誤編碼是很差處理的,由於是因爲編碼錯誤致使的。好的辦法其實重啓該進程,由於

  • 你不肯定某個編碼錯誤致使的錯誤會不會影響其它請求,好比創建數據庫連接錯誤因爲編碼錯誤致使不能成功,那麼其它錯誤將致使其它的請求也不可用

  • 或許在錯誤拋出以前進行IO操做,致使IO句柄沒法關閉,這將長期佔有內存,可能致使最後內存耗盡整個服務不可用。

  • 上面提到的兩點其實都沒有解決問題根本,應該在上線前作好測試,並在上線後作好監控,一旦發生相似的錯誤,就應該監控報警,關注並解決問題

如何分發錯誤

  • 在同步函數中,直接throw出錯誤

  • 對於一些異步函數,能夠將錯誤經過callback拋出

  • async/await能夠直接使用try..catch捕獲錯誤

  • EventEmitter拋出error事件

NodeJs的運維

一個NodeJs運用,僅僅從碼層面是很難保證穩定運行的,還要從運維層面去保障。

多進程來管理你的應用

單進程的nodejs一旦掛了,整個服務也就不可用了,因此我萌須要多個進程來保障服務的可用,某個進程只負責處理其它進程的啓動,關閉,重啓。保障某個進程掛掉後可以當即重啓。

能夠參考TSW中多進程的設計。master負責對worker的管理,worker和master保持這心跳監測,一旦失去,就當即重啓之。

domain
process.on('uncaughtException', function(err) {
    console.error('Error caught in uncaughtException event:', err);
});
process.on('unhandleRejection', function(err) {
  // TODO
})
複製代碼

上面捕獲nodejs中異常的時候,能夠說是很暴力。可是此時捕獲到異常的時候,你已經失去了此時的上下文,這裏的上下文能夠說是某個請求。假如某個web服務發生了一些異常的時候,仍是但願可以返回一些兜底的內容,提高用戶使用體驗。好比服務端渲染或者同構,即便失敗了,也能夠返回個靜態的html,走降級方案,可是此時的上下文已經丟失了。沒有辦法了。

function domainMiddleware(options) {
    return async function (ctx, next) {
        const request = ctx.request;
        const d = process.domain || domain.create();
        d.request = request;
        let errHandler = (err) => {
            ctx.set('Content-Type', 'text/html; charset=UTF-8');
            ctx.body = options.staticHtml;
        };
        d.on('error', errHandler);
        d.add(ctx.request);
        d.add(ctx.response);
        try {
            await next();
        } catch(e) {
            errHandler(e)
        }
    }
複製代碼

上面是一個簡單的koa2的domain的中間件,利用domain監聽error事件,每一個請求的Request, Response對象在發生錯誤的時候,均會觸發error 事件,當發生錯誤的時候,可以在有上下文的基礎上,能夠走降級方案。

如何避免內存泄露

內存泄漏很常見,特別是前端去寫後端程序,閉包運用不當,循環引用等都會致使內存泄漏。

  • 不要阻塞Event Loop的執行,特別是大循環或者IO同步操做

    for ( var i = 0; i < 10000000; i++ ) {
        var user       = {};
        user.name  = 'outmem';
        user.pass  = '123456';
        user.email = 'outmem[@outmem](/user/outmem).com';
    }
    複製代碼

    上面的很長的循環會致使內存泄漏,由於它是一個同步執行的代碼,將在進程中執行,V8在循環結束的時候,是沒辦法回收循環產生的內存的,這會致使內存一直增加。還有可能緣由是,這個很長的執行,阻塞了node進入下一個Event loop, 致使隊列中堆積了太多等待處理已經準備好的回調,進一步加重內存的佔用。那怎麼解決呢?

    能夠利用setTimeout將操做放在下一個loop中執行,減小長循環,同步IO對進程的阻.阻塞下一個loop 的執行,也會致使應用的性能降低

  • 模塊的私有變量和方法都會常駐在內存中

var leakArray = [];   
exports.leak = function () {  
  leakArray.push("leak" + Math.random());  
};
複製代碼

在node中require一個模塊的時候,最後都是造成一個單例,也就是隻要調用該函數一下,函數內存就會增加,閉包不會被回收,第二是leak方法是一個私有方法,這個方法也會一直存在內存。加入每一個請求都會調用一下這個方法,那麼內存一會就炸了。

這樣的場景其實很常見

// main.js
function Main() {
  this.greeting = 'hello world';
}
module.exports = Main;
複製代碼
var a = require('./main.js')();
var b = require('./main.js')();
a.greeting = 'hello a';
console.log(a.greeting); // hello a
console.log(b.greeting); // hello a
複製代碼

require獲得是一個單例,在一個服務端中每個請求執行的時候,操做的都是一個單例,這樣每一次執行產生的變量或者屬性都會一直掛在這個對象上,沒法回收,佔用大量內存。

其實上面能夠按照下面的調用方式來調用,每次都產生一個實例,用完回收。

var a = new require('./main.js');
// TODO
複製代碼

有的時候很難避免一些可能產生內存泄漏的問題,能夠利用vm每次調用都在一個沙箱環境下調用,用完回收調。

  • 最後就是避免循環引用了,這樣也會致使沒法回收
相關文章
相關標籤/搜索