js異常處理總結

js異常處理總結

先看最基礎的情況

function children() {
    throw new Error("子報錯");
}

function parent() {
    children(); //有異常拋出 函數中斷執行
}
parent();
console.log("cccccccc");

try catch 單層嵌套

function children() {
    throw new Error("子報錯");
}
function parent() {
    //可以在上一層函數捕獲下層函數的異常
    try {
        children(); //有異常拋出 函數中斷執行
    } catch(error) {
        console.log(error);
    }
}
parent();

多級嵌套,捕獲下面的異常

function children() {
    throw new Error("子報錯");
}

function parent() {
    children(); //有異常拋出 函數中斷執行
}

//多級嵌套也是沒問題的, 異常回層層往上拋
try {
    parent();
} catch (error) {
    console.log(error);
}
  1. 預期異常:參數不合法,前提條件不符合,通常直接throw
  2. 非預期異常: js運行時異常,來著依賴庫異常
  3. 可以直接在異常上面提供一下附加屬性來提供上下文
function children() {
    var err =  new Error("子報錯");
    err.statusCode = 404;  //附加的屬性 提供上下文
    throw err;
}

function parent() {
    children(); //有異常拋出 函數中斷執行
}

try {
    parent();
} catch (error) {
    console.log(error.statusCode); //404
    console.log(error);
}

異步回調異常處理

function asyncCallbackError(callback) {
    
    setTimeout(() => {
        throw new Error("異步出現了異常");
        callback();
    }, 0);
}

function callAsync() {
    asyncCallbackError(function() {
    });
}

//能不能在callAsync外面嵌套一個 try catch 處理異常呢?

try {
    callAsync();
} catch(error) {
    console.log(error);
}

上面的代碼是不行的,執行棧里面的try catch 無法捕獲異步隊列中拋出的異常

為啥 執行棧中的try catch無法捕獲到異步隊列的異常?
執行棧都執行完了,異步隊列才開始執行,所以執行棧無法捕獲異步
函數拋出的異常

所以對于異步函數,我們對異常的處理原則為, 異步函數里面使用自己的try catch 
自處理異常,然后通過它的回調函數的參數 callback(error, value) 在上一層的調用
中判斷是否異步出現了異常,如果出現的話, error即第一個參數不為空
function asyncCallbackError(callback) {
    
    setTimeout(() => {

        //異步函數必須自己處理自己的異常
        try {

            //if (發送了異常) {
            throw new Error("異步出現了異常");
            //}
            //else  沒有異常 {
            //callback(null, value);  無異常的話 第一個參數設置為null 第二個自己的值
            //}
        } catch(error) {
            callback(error);
        }
    }, 0);
}

function callAsync() {

    //callback 判斷獲取異步異常值
    asyncCallbackError((error, value) => {
        if (error) { //如果異步出現異常
            console.log(error);
        } else {
        }
    });
}

callAsync();

promise 異常

promise本身只是一個 基于事件分發的狀態管理器
它不為我們管理異常,所以promise里面的異常必須我們自己try catch
當 發生異常的時候 就設置當前 promise的狀態為 reject

十分注意一點是: 如果一個 reject狀態的promise沒有進行處理,那么它

var p = new Promise((resolve, reject) => {
    throw new Error("異常發生了");
    resolve();
});

console.log(p);

上面的代碼 會直接拋出一個異常,程序無法執行,因為promsie不會為我們自動處理異常

var p = new Promise((resolve, reject) => {
    try {
        throw new Error("異常發生了");
    } catch(error) {

        //不讓它的狀態立即改變,防止reject進入異步隊列,這樣可以讓then注冊的回調不是立即執行
        //tips: 一個promise狀態確定后,通過then注冊的回調 會立即執行
        setTimeout(() => {
            reject(error); //內部直接主動管理異常
        }, 0);
    }
});

p.then(() => {console.log("resolve")}, (error) => { console.log(error)});

promise內部發生異常,統一我們自己在內部try catch 處理,并設置它的狀態為reject
然后通過then 注冊reject回調處理,即promise異常處理通過reject狀態處理, 而且這里
再次強調一下,如果一個promise的reject狀態沒得到處理的話,會拋出一個異常

var p = new Promise(function (resolve, reject) {
    setTimeout(reject, 0);
  });

p.then(() => {});

上面的代碼報異常沒有捕獲,promise狀態為reject的話,需要處理reject情況
改成 p.then(()=>{}).catch(()=> {});就可以了

注意: 目前node里面Promise的reject沒有處理,拋的異常不會阻止程序的執行,但是
未來這種情況會中斷node的執行

generator的異常

如果在一個generator 函數體內拋出一個異常 它會怎么樣呢?

var g = function* () {
throw new Error("異常發生了");
yield console.log('yielding');
};

var i = g();
console.log(i.next());

結果程序遇到異常 也直接不執行了,所以generator也不會幫我們處理異常
我們需要自己手動處理

var g = function* () {
    try {
        throw new Error("異常發生了");
    } catch(error) {
        yield console.log('yielding');
    }

};

var i = g();
console.log(i.next());

generator 函數體內拋出的異常 還能在函數體外捕獲
但是注意捕獲的時間,哪個next 執行會拋出異常,就在哪次上門捕獲
當然也可以 try catch 包含多個next

var g = function* () {
    throw new Error("異常發生了");
    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("異常發生了");
};

var i = g();
try {
    i.next();
    i.next(); //第二個才會拋出異常
} catch(error) {
    console.log(error);
}

yield 自帶異常api Generator.prototype.throw()

Generator函數返回的遍歷器對象,都有一個throw方法,
可以在函數體外拋出錯誤,然后在Generator函數體內捕獲。

應該在 函數體里面的哪里捕獲呢? 想想 函數體外利用throw拋異常,
如何函數體內可以接受這個異常的話,那是不是說明現在的函數執行流程應該有
跑回到 generator函數里面,而generator會從上一個的yeild 左表達式開始執行,
所以,為了捕獲異常,我們應該 try catch 前一個yield

當然 它也是可以直接try catch 多個yeild 如果不確定那個拋出的話,只要在對應的
拋出異常的外傳yeild就可以了

var g = function* () {
    try {
        yield console.log('yielding'); //從這里斷開的 需要從左表達式處接受異常
    } catch(error) {
        console.log(error);
    }
};

var i = g();
i.next();
i.throw(new Error("外部異常")); //throw 傳遞的參數可以傳遞到generator里面

如果連續利用多個 generator.throw 那么如果異常無法在generator函數體內進行捕獲,
那么它就會在函數generator體外拋出這個異常 我們可以在體外處理

注意: throw的話 相當于 一個next 并且 同時throw Error 所以它會對生成器函數內部迭代一次

var g = function* () {
    try {
        yield console.log('yielding'); //從這里斷開的 需要從左表達式處接受異常
    } catch(error) {
        console.log(error);
    }
};

var i = g();
i.next();
i.throw(new Error("外部異常")); //throw 傳遞的參數可以傳遞到generator里面
i.throw(new Error("外部異常2"));  //這個異常無法在generator函數里面捕獲,所以它往外面拋 

//所以改寫為
var i = g();
i.next();
i.throw(new Error("外部異常")); //throw 傳遞的參數可以傳遞到generator里面
try {
    i.throw(new Error("外部異常2"));  //這個異常無法在generator函數里面捕獲,所以它往外面拋 
} catch (error) {
    console.log(error);
}

所以對于生成器函數來說 我們使用 它的 .throw函數拋出異常 在生成器函數里面使用 try catch 捕獲 yield異常,
內部無法捕獲的話,使用 try catch在對應的next處捕獲

await async異常

先直接在async里面拋出異常看看

async function f() {
  throw new Error('出錯了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)

上面并不會報錯, 不會終止程序的執行,因為async函數是一個自執行的generator,它里面會捕獲異常,
然后返回一個reject的promise, 所以我們可以在async里面把異常轉為promise的reject

所以async無論如何都會返回一個promise,如果內部有異常,那么返回一個reject的promise,
value為異常error, 如果返回一個值,那么返回一個resolve的promise,value為這個值,如果
返回一個promise,那么async就直接返回這個promise

只要有一個await后面的promise是reject, 那么async就會中斷,并且返回一個reject的promise,
(正如之前說過promise為reject的話,而且未處理,那么它會拋出一個異常)

那 如果我想 即使 await后面是一個reject的promise,我如何還能讓它往下執行呢?
可以用一個 try catch 將對應的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內部會幫我們處理異常? 如何幫的?

//代碼來自阮一峰es6

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    var gen = genF();
    function step(nextF) {

      //可以看到 每次迭代都將nextF用try catch 包裹起來, 
      //當生成器執行過程中拋出異常時,就可以在外部捕獲異常,并且立即返回 reject(e)      
      try {  
        var next = nextF();
      } catch(e) {
        return reject(e); //有異常的話 直接返回一個reject promise
      }
      //需要理解的是 為啥在這里捕獲異常??? 前面我們已經說過,生成器是一個迭代器,按照next的流程執行,
      // 只有next執行的時候,generator才會開始執行,執行才可能發生異常,而且生成   
      // 器在next執行期間,內部發生的
      //異常沒有被捕獲的話,可以往外拋,即再對應的next函數處,我們可以捕獲異常
      // nextF()的執行 恰好就是執行generator的next函數

      if(next.done) {
        return resolve(next.value);  //async的狀態直到 generator全部執行完 才確定
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        //發生錯誤,往生成器里面拋異常,如果這個異常在生成器內部沒有被捕獲,那么
       //它可以往外拋
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容