20.异常处理

本章介绍 JavaScript 如何处理异常。暂且不说:JavaScript 直到 ES3 才支持异常。这就解释了为什么它们被语言及其标准库谨慎使用。

20.1 动机:抛出和捕捉异常

请考虑以下代码。它将存储在文件中的配置文件读入具有类Profile实例的数组:

function readProfiles(filePaths) {
  const profiles = [];
  for (const filePath of filePaths) {
    try {
      const profile = readOneProfile(filePath);
      profiles.push(profile);
    } catch (err) { // (A)
      console.log('Error in: '+filePath, err);
    }
  }
}
function readOneProfile(filePath) {
  const profile = new Profile();
  const file = openFile(filePath);
  // ··· (Read the data in `file` into `profile`)
  return profile;
}
function openFile(filePath) {
  if (!fs.existsSync(filePath)) {
    throw new Error('Could not find file '+filePath); // (B)
  }
  // ··· (Open the file whose path is `filePath`)
}

让我们来看看 B 行发生了什么:发生错误,但处理问题的最佳位置不是当前位置,而是 A 行。在那里,我们可以跳过当前文件并转到下一个文件。因此:

  • 在 B 行中,我们使用throw语句来指示存在问题。
  • 在 A 行中,我们使用try-catch语句来处理问题。

当我们抛出时,以下构造是活动的:

readProfiles(···)
  for (const filePath of filePaths)
    try
      readOneProfile(···)
        openFile(···)
          if (!fs.existsSync(filePath))
            throw

throw走上这个构造链,直到找到try语句。在try语句的catch子句中继续执行。

20.2 throw

throw «value»;

可以抛出任何值,但最好抛出Error的实例:

throw new Error('Problem!');

20.2.1 用于创建错误对象的选项

  • 使用类Error。这在 JavaScript 中比在更静态的语言中更少限制,因为您可以将自己的属性添加到实例:
    const err = new Error('Could not find the file');
    err.filePath = filePath;
    throw err;
    
  • 使用Error的 JavaScript 子类之一(后面列出 )。
  • 子类Error自己。
    class MyError extends Error {
    }
    function func() {
      throw new MyError;
    }
    assert.throws(
      () => func(),
      MyError);
    

20.3 try-catch-finally

try语句的最大版本如下所示:

try {
  // try_statements
} catch (error) {
  // catch_statements
} finally {
  // finally_statements
}

try子句是必需的,但您可以省略catchfinally(但不能同时省略)。从 ECMAScript 2019 开始,如果您对抛出的值不感兴趣,也可以省略(error)

20.3.1 catch条款

如果在try块中抛出异常(并且之前未捕获),则将其分配给catch子句的参数,并执行该子句中的代码。除非它被指向其他地方(通过return或类似),否则在catch子句之后继续执行:使用finally子句 - 如果存在 - 或在try语句之后。以下代码演示了行 A 中抛出的值确实在行 B 中捕获。

const errorObject = new Error();
function func() {
  throw errorObject; // (A)
}
try {
  func();
} catch (err) { // (B)
  assert.equal(err, errorObject);
}

20.3.2 finally条款

让我们看一下finally的一个常见用例:你已经创建了一个资源,并希望在你完成它时总是销毁它 - 无论在使用它时发生了什么。你可以按如下方式实现:

const resource = createResource();
try {
  // Work with `resource`: errors may be thrown.
} finally {
  resource.destroy();
}

始终执行finally - 即使抛出错误(行 A):

let finallyWasExecuted = false;
assert.throws(
  () => {
    try {
      throw new Error(); // (A)
    } finally {
      finallyWasExecuted = true;
    }
  },
  Error
);
assert.equal(finallyWasExecuted, true);

始终执行finally - 即使有return语句(行 A):

let finallyWasExecuted = false;
function func() {
  try {
    return; // (A)
  } finally {
    finallyWasExecuted = true;
  }
}
func();
assert.equal(finallyWasExecuted, true);

20.4 错误类及其属性

引用 ECMAScript 规范

  • Error [根类]
    • RangeError:表示不在允许值的设置或范围内的值。
    • ReferenceError:表示检测到无效的参考值。
    • SyntaxError:表示发生了解析错误。
    • TypeError:用于表示当其他 NativeError 对象都不是故障原因的适当指示时不成功的操作。
    • URIError:表示其中一个全局 URI 处理函数的使用方式与其定义不兼容。

20.4.1 错误类的属性

考虑errError的一个实例:

const err = new Error('Hello!');
assert.equal(String(err), 'Error: Hello!');

err的两个属性特别有用:

  • .message:仅包含错误消息。
    assert.equal(err.message, 'Hello!');
    
  • .stack:包含堆栈跟踪。它受到所有主流浏览器的支持。
    assert.equal(
    err.stack,
    `
    Error: Hello!
        at Context.<anonymous> (ch_exception-handling.js:1:13)
    `.trim());
    
下一节:接下来的部分将解释可调用值的含义。