JavaScript 异常处理机制

JavaScript 程序运行错误被抛出后如果没有妥善的处理会导致程序停止继续运行,妥善的抛出和处理错误才能保证程序的健壮。

错误 Error

Error 对象是 JavaScript 的原生对象,当代码解析或运行时发生错误,JavaScript 引擎就会自动产生并抛出一个 error 对象实例,然后在错误发生的地方中断程序;一般包含以下属性:

  • name:错误类型
  • message:错误提示信息
  • stack:错误的堆栈(非标准属性,但是大多数平台支持)

原生错误类型

  1. SyntaxError:解析代码错误,写错了
  2. ReferenceError:引用错误,引用了一个不存在的变量或者对一个不允许改变的变量赋值(例:函数运行结果或 this 赋值)
  3. RangeError:值超过有效范围错误,数组下标负值等
  4. TypeError:类型错误
  5. URIError:URIError 是 URI 相关函数的参数不正确时抛出的错误,主要涉及 encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和 unescape()这六个函数
  6. EvalError:eval 函数没有被正确执行时,会抛出 EvalError 错误,

自定义错误及错误抛出

Error 是基类,其它错误类型都继承自此基类 throw用于抛出错误,可以接受各种值作为参数。一个错误一旦被抛出就会在程序栈中向上冒泡,如果没有在某个位置被捕获,这个错误会导致程序崩溃停止继续运行。

function UserError(message) {
  this.message = message || "默认信息";
  this.name = "UserError";
}

UserError.prototype = Object.create(Error.prototype);
UserError.prototype.constructor = UserError;

UserError.prototype.toString = function () {
  return this.name + ': "' + this.message + '"';
};

throw new UserError("出错了!");

异常捕获

JS 引擎在执行过程中遇到异常会抛出异常并停止继续运行。我们可以使用try catch预警捕获程序抛出的错误并处理,防止程序崩溃。

function f() {
  try {
    console.log(0);
    throw "bug";
  } catch (e) {
    console.log(1);
    return true; // 这句原本会延迟到finally代码块结束再执行
    console.log(2); // 不会运行
  } finally {
    console.log(3);
    return false; // 这句会覆盖掉 catch 中的 return
    console.log(4); // 不会运行
  }
  console.log(5); // 不会运行
}
console.log(f()); // 0 1 3 false

异步异常处理

在异步函数中我们可以和在同步代码中一样使用try catch捕获异步程序抛出的错误,因为回调函数的执行上下文与原函数是分离的,详见:Async 异步函数

async function task(value) {
  throw TypeError("test error");
  return value;
}
async function main() {
  try {
    await task('test');
  } catch (error) {
    console.log(`捕获错误:${error.message}`);
  } finally {
    console.log("Always runs!");
  }
}
main();

Promise异常处理

Promise中抛出的错误只能通过.then()方法的第二个回调函数或.catch()方法捕获,无论是reject或者throw new Error,都可以通过.catch()回调捕获。

const p1 = new Promise((resolve, reject) => {
  reject();
});
p1.catch((e) => console.log("p1 error"));

const p2 = new Promise((resolve, reject) => {
  throw new Error("p2 error");
});
p2.catch((e) => console.log("p2 error"));

Promise 的then的第二个参数和catch的区别是如果在then的第一个函数里抛出了异常,后面的catch能捕获到,而then的第二个函数捕获不到。

Promise 内部的异步任务中的reject也可以使用promise.catch捕获,但是异步任务中throw的错误无法使用promise.catch捕捉

const p3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      throw new "async error"();
    });
  });
};

function main3() {
  // catch无法捕获p3中抛出的异常
  p3().catch((e) => console.log(e));
}
main3();

根本原因还是因为同步函数无法捕捉异步函数抛出的异常,解决方案是将异步任务 Promise 化

const p4 = () =>  new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error("async error"));
  })
});

function main() {
  p4().catch(e => console.log(e));
}
main();

同步函数在处理异步任务异常

同步任务中可以使用try catch语句可以捕获程序异常,但是无法捕获异步任务抛出的异常

  1. 同步函数无法捕捉异步函数抛出的异常
  2. 同步执行异步函数,无法捕捉被执行异步函数抛出的异常
function main() {
  try {
    setTimeout(() => {
      throw new Error("async error");
    }, 1000);
  } catch (e) {
    console.log(e, "err");
  }
}
main();
// 异常未捕获,程序中断,下面的语句不会执行
console.log("continue...");

这段代码在setTimeoutthrow的错误并没有被捕捉到,程序直接报错崩溃。JavaScript 异步任务是在独立的进程中执行,上面的代码在异步任务在执行完成加入 event loop 时,主进程已经执行完成出栈,这时主进程的执行上下文已经改变,这时主线程中的try无法捕获callback中抛出的错误。参考JavaScript 运行机制

如果想要使用try catch捕获异步任务中发生的异常,需要使用 Promise 将异步任务 Promise 化

相关阅读与参考

avaScript 错误处理完全指南 JS 异步错误捕获二三事

TODO:error-first 约定

异常是否抛出取决于回调函数是否执行,异常的控制权不在业务方

function fetch(handleError, callback) {
  setTimeout(() => {
    handleError("请求失败");
  });
}

fetch(
  () => {
    console.log("失败处理"); // 失败处理
  },
  (error) => {
    console.log("请求处理"); // 永远不会执行
  }
);

TODO:全局异常捕获兜底

results matching ""

    No results matching ""