错误处理

try-catch

完整语法:

try {
  // 代码...
} catch (err) {
  // 错误捕获
  // 若无错误,自动忽略
} finally {
  // 无论是否发生错误都会执行
}
  • 首先,一行一行执行try块的内容
  • 执行遇到错误时,引擎会控制流转向catch块,进行错误处理,否则忽略catch模块
  • 无论是否有错误,最后都会执行finally块内容

    finally 子句适用于 try...catch任何 出口,包括显式的 return

运行与解析

错误处理只能对于运行时错误进行捕获,而对于解析时间发生的错误则无能为力

  • JavaScript脚本首先会被引擎进行解析,处于解析时间,主要处理语法错误
  • 解析完成后会进入引擎进行解释运行,进入运行时间try-catch开始工作

异步错误

try-catch无法在外部处理非同步代码的错误

try {
  setTimeout(function() {
    noSuchVariable; // 脚本将在这里停止运行
  }, 1000);
} catch (err) {
  console.info("不工作");
}
  • setTimeout部分属于计划执行函数,不会立即执行
  • 当开始执行setTimeout时,意味着引擎已经离开了同步代码块try-catch
  • 此时的错误无法被try-catch捕获处理

正确应该是放入setTimeout内部进行捕获

setTimeout(function() {
  try {
    noSuchVariable; // try...catch 处理 error 了!
  } catch {
    console.info("error 被在这里捕获了!");
  }
}, 1000);

Error对象

当错误发生时,会将错误的相关信息全部放入catch (err)中的err对象(命名可以随意)中。也允许不捕获错误对象的数据,直接 catch {} 即可

Error对象结构:

  • name:错误的名称(ReferenceErrorSyntaxError等)
  • message:错误对象创建时传入的错误描述
  • stack:包含有关导致 error 的嵌套调用序列的信息,用于调试,非标准实现
try {
  lalala; // error, variable is not defined!
} catch (err) {
  console.info(err.name); // ReferenceError
  console.info(err.message); // lalala is not defined
  console.info(err.stack); // ReferenceError: lalala is not defined at (...call stack)

  // 也可以将一个 error 作为整体显示出来
  // error 信息被转换为像 "name: message" 这样的字符串
  console.info(err); // ReferenceError: lalala is not defined
}

抛出错误

语法:

throw error_object;

// 示例
let error = new SyntaxError(message);
throw error;

// 或者
throw new SyntaxError(message);

建议做法:捕获错误时,只处理特定的错误,对于其余未知的错误,使用throw重新抛出,让上一级进行处理。

错误类型判断方法:

  1. err instanceof ReferenceError
  2. 读取err.name
  3. 读取err.constructor.name

全局catch

一般用于浏览器环境。

Node中:通过给程序订阅一个事件进行全局处理

process.on("uncaughtException")

浏览器中:给全局添加一个错误处理方法即可,一般是给开发人员或者用户进行提醒,无法进行修复。
可以在onerror函数中添加网络请求,发生错误时将错误信息发送到服务提供方进行分析。

window.onerror = function(message, url, line, col, error) {
  // ...
};
  • messageerror信息。
  • url:发生error的脚本的URL
  • linecol:发生error处的代码的行号和列号。
  • errorerror对象。

只能在网页中复现:

<script>
    window.onerror = function(message, url, line, col, error) {
        console.info(`${message}\n At ${line}:${col} of ${url}`);
    };

    function readData() {
        badFunc(); // 啊,出问题了!
    }

    readData();
</script>

自定义Error

扩展Error

可以根据当前的错误场景进行定义合适的错误类型

class ValidationError extends Error {
  constructor(message) {
    // 必须先调用基类的构造函数
    super(message);
    this.name = "ValidationError";
  }
}

// 用法
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new ValidationError("No field: age");
  }
  if (!user.name) {
    throw new ValidationError("No field: name");
  }

  return user;
}

// try..catch 的工作示例

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    console.info("Invalid data: " + err.message); // Invalid data: No field: name
  } else if (err instanceof SyntaxError) { // (*)
    console.info("JSON Syntax Error: " + err.message);
  } else {
    throw err; // 未知的 error,再次抛出 (**)
  }
}

深入继承

可以对自定义的错误类进行再次细化

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

class PropertyRequiredError extends ValidationError {
  constructor(property) {
    super("No property: " + property);
    this.name = "PropertyRequiredError";
    this.property = property;
  }
}

// 用法
function readUser(json) {
  let user = JSON.parse(json);

  if (!user.age) {
    throw new PropertyRequiredError("age");
  }
  if (!user.name) {
    throw new PropertyRequiredError("name");
  }

  return user;
}

// try..catch 的工作示例

try {
  let user = readUser('{ "age": 25 }');
} catch (err) {
  if (err instanceof ValidationError) {
    console.info("Invalid data: " + err.message); // Invalid data: No property: name
    console.info(err.name); // PropertyRequiredError
    console.info(err.property); // name
  } else if (err instanceof SyntaxError) {
    console.info("JSON Syntax Error: " + err.message);
  } else {
    throw err; // 未知 error,将其再次抛出
  }
}

包装异常

对于同一场景下发生的不同错误,可以统一往上层抛出一个异常。
当我们只需要知道这里发生了哪种错误,而不需要具体的错误信息时使用。

class ReadError extends Error {
  constructor(message, cause) {
    super(message);
    this.cause = cause;
    this.name = 'ReadError';
  }
}

class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }

function validateUser(user) {
  if (!user.age) {
    throw new PropertyRequiredError("age");
  }

  if (!user.name) {
    throw new PropertyRequiredError("name");
  }
}

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
    if (err instanceof SyntaxError) {
      throw new ReadError("Syntax Error", err);
    } else {
      throw err;
    }
  }

  try {
    validateUser(user);
  } catch (err) {
    // 实际发生的 ValidationError
    // 但往上层抛出 ReadError
    if (err instanceof ValidationError) {
      throw new ReadError("Validation Error", err);
    } else {
      throw err;
    }
  }

}

try {
  readUser('{bad json}');
} catch (e) {
  if (e instanceof ReadError) {
    console.info(e);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    console.info("Original error: " + e.cause);
  } else {
    throw e;
  }
}