js異常處理總結(jié)
先看最基礎(chǔ)的情況
function children() {
throw new Error("子報錯");
}
function parent() {
children(); //有異常拋出 函數(shù)中斷執(zhí)行
}
parent();
console.log("cccccccc");
try catch 單層嵌套
function children() {
throw new Error("子報錯");
}
function parent() {
//可以在上一層函數(shù)捕獲下層函數(shù)的異常
try {
children(); //有異常拋出 函數(shù)中斷執(zhí)行
} catch(error) {
console.log(error);
}
}
parent();
多級嵌套,捕獲下面的異常
function children() {
throw new Error("子報錯");
}
function parent() {
children(); //有異常拋出 函數(shù)中斷執(zhí)行
}
//多級嵌套也是沒問題的, 異吵踝梗回層層往上拋
try {
parent();
} catch (error) {
console.log(error);
}
- 預(yù)期異常:參數(shù)不合法炸站,前提條件不符合偏窝,通常直接throw
- 非預(yù)期異常: js運(yùn)行時異常,來著依賴庫異常
- 可以直接在異常上面提供一下附加屬性來提供上下文
function children() {
var err = new Error("子報錯");
err.statusCode = 404; //附加的屬性 提供上下文
throw err;
}
function parent() {
children(); //有異常拋出 函數(shù)中斷執(zhí)行
}
try {
parent();
} catch (error) {
console.log(error.statusCode); //404
console.log(error);
}
異步回調(diào)異常處理
function asyncCallbackError(callback) {
setTimeout(() => {
throw new Error("異步出現(xiàn)了異常");
callback();
}, 0);
}
function callAsync() {
asyncCallbackError(function() {
});
}
//能不能在callAsync外面嵌套一個 try catch 處理異常呢梁钾?
try {
callAsync();
} catch(error) {
console.log(error);
}
上面的代碼是不行的绳泉,執(zhí)行棧里面的try catch 無法捕獲異步隊(duì)列中拋出的異常
為啥 執(zhí)行棧中的try catch無法捕獲到異步隊(duì)列的異常?
執(zhí)行棧都執(zhí)行完了姆泻,異步隊(duì)列才開始執(zhí)行零酪,所以執(zhí)行棧無法捕獲異步
函數(shù)拋出的異常
所以對于異步函數(shù),我們對異常的處理原則為, 異步函數(shù)里面使用自己的try catch
自處理異常拇勃,然后通過它的回調(diào)函數(shù)的參數(shù) callback(error, value) 在上一層的調(diào)用
中判斷是否異步出現(xiàn)了異常四苇,如果出現(xiàn)的話, error即第一個參數(shù)不為空
function asyncCallbackError(callback) {
setTimeout(() => {
//異步函數(shù)必須自己處理自己的異常
try {
//if (發(fā)送了異常) {
throw new Error("異步出現(xiàn)了異常");
//}
//else 沒有異常 {
//callback(null, value); 無異常的話 第一個參數(shù)設(shè)置為null 第二個自己的值
//}
} catch(error) {
callback(error);
}
}, 0);
}
function callAsync() {
//callback 判斷獲取異步異常值
asyncCallbackError((error, value) => {
if (error) { //如果異步出現(xiàn)異常
console.log(error);
} else {
}
});
}
callAsync();
promise 異常
promise本身只是一個 基于事件分發(fā)的狀態(tài)管理器
它不為我們管理異常方咆,所以promise里面的異常必須我們自己try catch
當(dāng) 發(fā)生異常的時候 就設(shè)置當(dāng)前 promise的狀態(tài)為 reject
十分注意一點(diǎn)是: 如果一個 reject狀態(tài)的promise沒有進(jìn)行處理月腋,那么它
var p = new Promise((resolve, reject) => {
throw new Error("異常發(fā)生了");
resolve();
});
console.log(p);
上面的代碼 會直接拋出一個異常,程序無法執(zhí)行,因?yàn)閜romsie不會為我們自動處理異常
var p = new Promise((resolve, reject) => {
try {
throw new Error("異常發(fā)生了");
} catch(error) {
//不讓它的狀態(tài)立即改變榆骚,防止reject進(jìn)入異步隊(duì)列片拍,這樣可以讓then注冊的回調(diào)不是立即執(zhí)行
//tips: 一個promise狀態(tài)確定后,通過then注冊的回調(diào) 會立即執(zhí)行
setTimeout(() => {
reject(error); //內(nèi)部直接主動管理異常
}, 0);
}
});
p.then(() => {console.log("resolve")}, (error) => { console.log(error)});
promise內(nèi)部發(fā)生異常妓肢,統(tǒng)一我們自己在內(nèi)部try catch 處理捌省,并設(shè)置它的狀態(tài)為reject
然后通過then 注冊reject回調(diào)處理,即promise異常處理通過reject狀態(tài)處理, 而且這里
再次強(qiáng)調(diào)一下碉钠,如果一個promise的reject狀態(tài)沒得到處理的話纲缓,會拋出一個異常
var p = new Promise(function (resolve, reject) {
setTimeout(reject, 0);
});
p.then(() => {});
上面的代碼報異常沒有捕獲,promise狀態(tài)為reject的話喊废,需要處理reject情況
改成 p.then(()=>{}).catch(()=> {});
就可以了
注意: 目前node里面Promise的reject沒有處理祝高,拋的異常不會阻止程序的執(zhí)行,但是
未來這種情況會中斷node的執(zhí)行
generator的異常
如果在一個generator 函數(shù)體內(nèi)拋出一個異常 它會怎么樣呢污筷?
var g = function* () {
throw new Error("異常發(fā)生了");
yield console.log('yielding');
};
var i = g();
console.log(i.next());
結(jié)果程序遇到異常 也直接不執(zhí)行了工闺,所以generator也不會幫我們處理異常
我們需要自己手動處理
var g = function* () {
try {
throw new Error("異常發(fā)生了");
} catch(error) {
yield console.log('yielding');
}
};
var i = g();
console.log(i.next());
generator 函數(shù)體內(nèi)拋出的異常 還能在函數(shù)體外捕獲
但是注意捕獲的時間,哪個next 執(zhí)行會拋出異常颓屑,就在哪次上門捕獲
當(dāng)然也可以 try catch 包含多個next
var g = function* () {
throw new Error("異常發(fā)生了");
yield console.log('yielding');
};
var i = g();
try {
console.log(i.next());
} catch(error) {
console.log(error);
}
嵌套多個next 可能拋出的異常 異常
var g = function* () {
yield console.log('yielding');
throw new Error("異常發(fā)生了");
};
var i = g();
try {
i.next();
i.next(); //第二個才會拋出異常
} catch(error) {
console.log(error);
}
yield 自帶異常api Generator.prototype.throw()
Generator函數(shù)返回的遍歷器對象孽尽,都有一個throw方法茁瘦,
可以在函數(shù)體外拋出錯誤,然后在Generator函數(shù)體內(nèi)捕獲祥款。
應(yīng)該在 函數(shù)體里面的哪里捕獲呢罗侯? 想想 函數(shù)體外利用throw拋異常器腋,
如何函數(shù)體內(nèi)可以接受這個異常的話,那是不是說明現(xiàn)在的函數(shù)執(zhí)行流程應(yīng)該有
跑回到 generator函數(shù)里面钩杰,而generator會從上一個的yeild 左表達(dá)式開始執(zhí)行纫塌,
所以,為了捕獲異常讲弄,我們應(yīng)該 try catch 前一個yield
當(dāng)然 它也是可以直接try catch 多個yeild 如果不確定那個拋出的話措左,只要在對應(yīng)的
拋出異常的外傳yeild就可以了
var g = function* () {
try {
yield console.log('yielding'); //從這里斷開的 需要從左表達(dá)式處接受異常
} catch(error) {
console.log(error);
}
};
var i = g();
i.next();
i.throw(new Error("外部異常")); //throw 傳遞的參數(shù)可以傳遞到generator里面
如果連續(xù)利用多個 generator.throw 那么如果異常無法在generator函數(shù)體內(nèi)進(jìn)行捕獲,
那么它就會在函數(shù)generator體外拋出這個異常 我們可以在體外處理
注意: throw的話 相當(dāng)于 一個next 并且 同時throw Error 所以它會對生成器函數(shù)內(nèi)部迭代一次
var g = function* () {
try {
yield console.log('yielding'); //從這里斷開的 需要從左表達(dá)式處接受異常
} catch(error) {
console.log(error);
}
};
var i = g();
i.next();
i.throw(new Error("外部異常")); //throw 傳遞的參數(shù)可以傳遞到generator里面
i.throw(new Error("外部異常2")); //這個異常無法在generator函數(shù)里面捕獲避除,所以它往外面拋
//所以改寫為
var i = g();
i.next();
i.throw(new Error("外部異常")); //throw 傳遞的參數(shù)可以傳遞到generator里面
try {
i.throw(new Error("外部異常2")); //這個異常無法在generator函數(shù)里面捕獲怎披,所以它往外面拋
} catch (error) {
console.log(error);
}
所以對于生成器函數(shù)來說 我們使用 它的 .throw函數(shù)拋出異常 在生成器函數(shù)里面使用 try catch 捕獲 yield異常,
內(nèi)部無法捕獲的話瓶摆,使用 try catch在對應(yīng)的next處捕獲
await async異常
先直接在async里面拋出異沉构洌看看
async function f() {
throw new Error('出錯了');
}
f().then(
v => console.log(v),
e => console.log(e)
)
上面并不會報錯, 不會終止程序的執(zhí)行,因?yàn)閍sync函數(shù)是一個自執(zhí)行的generator群井,它里面會捕獲異常状飞,
然后返回一個reject的promise, 所以我們可以在async里面把異常轉(zhuǎn)為promise的reject
所以async無論如何都會返回一個promise,如果內(nèi)部有異常,那么返回一個reject的promise,
value為異常error, 如果返回一個值诬辈,那么返回一個resolve的promise,value為這個值酵使,如果
返回一個promise,那么async就直接返回這個promise
只要有一個await后面的promise是reject自晰, 那么async就會中斷凝化,并且返回一個reject的promise,
(正如之前說過promise為reject的話,而且未處理酬荞,那么它會拋出一個異常)
那 如果我想 即使 await后面是一個reject的promise搓劫,我如何還能讓它往下執(zhí)行呢?
可以用一個 try catch 將對應(yīng)的await 包裹住混巧,這樣的話枪向,它就可以捕獲promise未處理拋出的異常
或者把這個promise給處理了
async 處理promise reject異常的方法
try catch 方法
async function f() {
try {
await Promise.reject('出錯了');
} catch(e) {
}
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// hello world
把未處理的 promise給處理了 即利用.catch 這樣的話 返回一個resolve的promise
async function f() {
await Promise.reject('出錯了')
.catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f()
.then(v => console.log(v))
// 出錯了
// hello world
為何async內(nèi)部會幫我們處理異常? 如何幫的咧党?
//代碼來自阮一峰es6
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
//可以看到 每次迭代都將nextF用try catch 包裹起來,
//當(dāng)生成器執(zhí)行過程中拋出異常時秘蛔,就可以在外部捕獲異常,并且立即返回 reject(e)
try {
var next = nextF();
} catch(e) {
return reject(e); //有異常的話 直接返回一個reject promise
}
//需要理解的是 為啥在這里捕獲異常傍衡?深员?? 前面我們已經(jīng)說過蛙埂,生成器是一個迭代器倦畅,按照next的流程執(zhí)行,
// 只有next執(zhí)行的時候绣的,generator才會開始執(zhí)行叠赐,執(zhí)行才可能發(fā)生異常,而且生成
// 器在next執(zhí)行期間屡江,內(nèi)部發(fā)生的
//異常沒有被捕獲的話芭概,可以往外拋,即再對應(yīng)的next函數(shù)處惩嘉,我們可以捕獲異常
// nextF()的執(zhí)行 恰好就是執(zhí)行g(shù)enerator的next函數(shù)
if(next.done) {
return resolve(next.value); //async的狀態(tài)直到 generator全部執(zhí)行完 才確定
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
//發(fā)生錯誤罢洲,往生成器里面拋異常,如果這個異常在生成器內(nèi)部沒有被捕獲文黎,那么
//它可以往外拋
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}