JavaScript的異常處理

JavaScript 引擎執行 JavaScript 代碼時,有可能會發生各類異常,例如是語法異常,語言中缺乏的功能,因爲來自服務器或用戶的異常輸出而致使的異常。html

Javascript 引擎是單線程的,所以一旦遇到異常,Javascript 引擎一般會中止執行,阻塞後續代碼並拋出一個異常信息,所以對於可預見的異常,咱們應該捕捉並正確展現給用戶或開發者。html5

Error對象

throwPromise.reject() 能夠拋出字符串類型的異常,並且能夠拋出一個 Error 對象類型的異常。express

一個 Error 對象類型的異常不只包含一個異常信息,同時也包含一個追溯棧這樣你就能夠很容易經過追溯棧找到代碼出錯的行數了。segmentfault

因此推薦拋出 Error 對象類型的異常,而不是字符串類型的異常。跨域

建立本身的異常構造函數promise

function MyError(message) {
    var instance = new Error(message);
    instance.name = 'MyError';
    Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
    return instance;
}

MyError.prototype = Object.create(Error.prototype, {
    constructor: {
        value: MyError,
        enumerable: false,
        writable: true,
        configurable: true
    }
});

if (Object.setPrototypeOf) {
    Object.setPrototypeOf(MyError, Error);
} else {
    MyError.__proto__ = Error;
}

export default MyError;

在代碼中拋出自定義的異常類型並捕捉服務器

try {
    throw new MyError("some message");
} catch(e){
    console.log(e.name + ":" + e.message);
}

Throw

throw expression;

throw 語句用來拋出一個用戶自定義的異常。當前函數的執行將被中止(throw 以後的語句將不會執行),而且控制將被傳遞到調用堆棧中的第一個 catch 塊。若是調用者函數中沒有 catch 塊,程序將會終止。異步

try {
    console.log('before throw error');
    throw new Error('throw error');
    console.log('after throw error');
} catch (err) {
    console.log(err.message);
}

// before throw error
// throw error

Try / Catch

try {
   try_statements
}
[catch (exception) {
   catch_statements
}]
[finally {
   finally_statements
}]

try/catch 主要用於捕捉異常。try/catch 語句包含了一個 try 塊, 和至少有一個 catch 塊或者一個 finally 塊,下面是三種形式的 try 聲明:函數

  • try...catch
  • try...finally
  • try...catch...finally

try 塊中放入可能會產生異常的語句或函數性能

catch 塊中包含要執行的語句,當 try 塊中拋出異常時,catch 塊會捕捉到這個異常信息,並執行 catch 塊中的代碼,若是在 try 塊中沒有異常拋出,這 catch 塊將會跳過。

finally 塊在 try 塊和 catch 塊以後執行。不管是否有異常拋出或着是否被捕獲它老是執行。當在 finally 塊中拋出異常信息時會覆蓋掉 try 塊中的異常信息。

try {
    try {
        throw new Error('can not find it1');
    } finally {
        throw new Error('can not find it2');
    }
} catch (err) {
    console.log(err.message);
}

// can not find it2

若是從 finally 塊中返回一個值,那麼這個值將會成爲整個 try-catch-finally 的返回值,不管是否有 return 語句在 trycatch 中。這包括在 catch 塊裏拋出的異常。

function test() {
    try {
        throw new Error('can not find it1');
        return 1;
    } catch (err) {
        throw new Error('can not find it2');
        return 2;
    } finally {
        return 3;
    }
}

console.log(test()); // 3

Try / Catch 性能

有一個你們衆所周知的反優化模式就是使用 try/catch

在V8(其餘JS引擎也可能出現相同狀況)函數中使用了 try/catch 語句不可以被V8編譯器優化。參考http://www.html5rocks.com/en/tutorials/speed/v8/

window.onerror

經過在 window.onerror 上定義一個事件監聽函數,程序中其餘代碼產生的未被捕獲的異常每每就會被 window.onerror 上面註冊的監聽函數捕獲到。而且同時捕獲到一些關於異常的信息。

window.onerror = function (message, source, lineno, colno, error) { }
  • message:異常信息(字符串)
  • source:發生異常的腳本URL(字符串)
  • lineno:發生異常的行號(數字)
  • colno:發生異常的列號(數字)
  • error:Error對象(對象)

注意:Safari 和 IE10 還不支持在 window.onerror 的回調函數中使用第五個參數,也就是一個 Error 對象並帶有一個追溯棧

try/catch 不可以捕獲異步代碼中的異常,可是其將會把異常拋向全局而後 window.onerror 能夠將其捕獲。

try {
    setTimeout(() => {
        throw new Error("some message");
    }, 0);
} catch (err) {
    console.log(err);
}
// Uncaught Error: some message
window.onerror = (msg, url, line, col, err) => {
    console.log(err);
}
setTimeout(() => {
    throw new Error("some message");
}, 0);
// Error: some message

在Chrome中,window.onerror 可以檢測到從別的域引用的script文件中的異常,而且將這些異常標記爲Script error。若是你不想處理這些從別的域引入的script文件,那麼能夠在程序中經過Script error標記將其過濾掉。然而,在Firefox、Safari或者IE11中,並不會引入跨域的JS異常,即便在Chrome中,若是使用 try/catch 將這些討厭的代碼包圍,那麼Chrome也不會再檢測到這些跨域異常。

在Chrome中,若是你想經過 window.onerror 來獲取到完整的跨域異常信息,那麼這些跨域資源必須提供合適的跨域頭信息。

Promise中的異常

Promise中拋出異常

new Promise((resolve,reject)=>{
    reject();
})
Promise.resolve().then((resolve,reject)=>{
    reject();
});
Promise.reject();
throw expression;

Promise中捕捉異常

promiseObj.then(undefined, (err)=>{
    catch_statements
});
promiseObj.catch((exception)=>{
    catch_statements
})

JavaScript 函數中,只有 return / yield / throw 會中斷函數的執行,其餘的都沒法阻止其運行到結束的。

resolve / reject 以前加上 return 能阻止往下繼續運行。

without return

Promise.resolve()
.then(() => {
    console.log('before excute reject');
    reject(new Error('throw error'));
    console.log('after excute reject');
})
.catch((err) => {
    console.log(err.message);
});

// before excute reject
// throw error
// after excute reject

use return

Promise.resolve()
.then(() => {
    console.log('before excute reject');
    return reject(new Error('throw error'));
    console.log('after excute reject');
})
.catch((err) => {
    console.log(err.message);
});

// before excute reject
// throw error

Throw or Reject

不管是 try/catch 仍是 promise 都能捕獲到的是「同步」異常

reject 是回調,而 throw 只是一個同步的語句,若是在另外一個異步的上下文中拋出,在當前上下文中是沒法捕獲到的。

所以在 Promise 中使用 reject 拋出異常。不然 catch 有可能會捕捉不到。

Promise.resolve()
.then(() => {
    setTimeout(()=>{
        throw new Error('throw error');
    },0);
})
.catch((err) => {
    console.log(err);
});

// Uncaught Error: throw error
Promise.resolve()
.then(() => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error('throw error'));
        }, 0);
    });
})
.catch((err) => {
    console.log(err);
});

// Error: throw error

window.onunhandledrejection

window.onunhandledrejectionwindow.onerror 相似,在一個JavaScript Promise 被 reject 可是沒有 catch 來捕捉這個 reject時觸發。而且同時捕獲到一些關於異常的信息。

window.onunhandledrejection = event => { 
    console.log(event.reason);
}

event事件是 PromiseRejectionEvent 的實例,它有兩個屬性:

  • event.promise:被 rejected 的 JavaScript Promise
  • event.reason:一個值或 Object 代表爲何 promise 被 rejected,是 Promise.reject() 中的內容。

window.rejectionhandled

由於 Promise 能夠延後調用 catch 方法,若在拋出 reject 時未調用 catch 進行捕捉,但稍後再次調用 catch,此時會觸發 rejectionhandled 事件。

window.onrejectionhandled = event =>
{
    console.log('rejection handled');
}

let p = Promise.reject(new Error('throw error'));

setTimeout(()=>{
    p.catch(e=>{console.log(e)});
},1000);

// Uncaught (in promise) Error: throw error
// 1秒後輸出
// Error: throw error
// rejection handled

統一異常處理

代碼中拋出的異常,一種是要展現給用戶,一種是展現給開發者。

對於展現給用戶的異常,通常使用 alerttoast 展現;對於展現給開發者的異常,通常輸出到控制檯。

在一個函數或一個代碼塊中能夠把拋出的異常統一捕捉起來,按照不一樣的異常類型以不一樣的方式展現,對於。

須要點擊確認的異常類型:
ensureError.js

function EnsureError(message = 'Default Message') {
    this.name = 'EnsureError';
    this.message = message;
    this.stack = (new Error()).stack;
}
EnsureError.prototype = Object.create(Error.prototype);
EnsureError.prototype.constructor = EnsureError;

export default EnsureError;

彈窗提示的異常類型:
toastError.js

function ToastError(message = 'Default Message') {
    this.name = 'ToastError';
    this.message = message;
    this.stack = (new Error()).stack;
}
ToastError.prototype = Object.create(Error.prototype);
ToastError.prototype.constructor = ToastError;

export default ToastError;

提示開發者的異常類型:
devError.js

function DevError(message = 'Default Message') {
    this.name = 'ToastError';
    this.message = message;
    this.stack = (new Error()).stack;
}
DevError.prototype = Object.create(Error.prototype);
DevError.prototype.constructor = DevError;

export default DevError;

異常處理器:
拋出普通異常時,能夠帶上 stackoverflow 上問題的列表,方便開發者查找緣由。
errorHandler.js

import EnsureError from './ensureError.js';
import ToastError from './toastError.js';
import DevError from './devError.js';
import EnsurePopup from './ensurePopup.js';
import ToastPopup from './toastPopup.js';

function errorHandler(err) {
    if (err instanceof EnsureError) {
        EnsurePopup(err.message);
    } else if (err instanceof ToastError) {
        ToastPopup(err.message);
    }else if( err instanceof DevError){
        DevError(err.message);
    }else{
        error.message += ` https://stackoverflow.com/questions?q=${encodeURI(error.message)}`
        console.error(err.message);    
    }
}

window.onerror = (msg, url, line, col, err) => {
    errorHandler(err);
}

window.onunhandledrejection = event =>{
    errorHandler(event.reason);
};

export default errorHandler;

歡迎關注:Leechikit
原文連接:segmentfault.com

到此本文結束,歡迎提問和指正。寫原創文章不易,若本文對你有幫助,請點贊、推薦和關注做者支持。

相關文章
相關標籤/搜索