发布于 2026-01-06 0 阅读
0

JavaScript 异步代码中的异常处理 Try…Catch Try 块 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

JavaScript 中的异常处理

尝试…捕捉

异步代码中的 try 块

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

立即订阅我的邮件列表:http: //jauyeung.net/subscribe/

请在Twitter上关注我:https://twitter.com/AuMayeung

更多文章请访问https://medium.com/@hohanga

和所有程序一样,JavaScript 也会遇到错误,例如 JSON 解析失败或变量中意外出现空值。这意味着,如果我们希望应用程序为用户提供良好的用户体验,就必须优雅地处理这些错误。错误通常以异常的形式出现,因此我们必须优雅地处理它们。为了处理try...catch这些错误,我们需要使用相应的语句来处理它们,以避免程序崩溃。

尝试…捕捉

要使用该try...catch代码块,我们需要使用以下语法:

try{  
  // code that we run that may raise exceptions  
  // one or more lines is required in this block  
}  
catch (error){  
  // handle error here  
  // optional if finally block is present  
}  
finally {  
  // optional code that run either   
  // when try or catch block is finished  
}

例如,我们可以编写以下代码来捕获异常:

try {  
  undefined.prop  
} catch (error) {  
  console.log(error);  
}

在上面的代码中,我们试图从 `<object>` 中获取属性undefined,这显然是不允许的,因此抛出了异常。在代码块中catch,我们捕获了由运行引起的“TypeError: Cannot read property 'prop' of undefined”异常,并将异常undefined.prop输出记录下来。这样,我们就得到了输出的错误信息,而不是导致程序崩溃。

try...catch语句包含一个try代码块。try代码块内必须至少包含一条语句,并且必须始终使用花括号,即使是单条语句也一样。然后,可以包含catch`if` 或finally`if` 子句。这意味着我们可以有:

try {  
  ...  
}   
catch {  
  ...  
}

try {  
  ...  
}   
finally{  
  ...  
}

try {  
  ...  
}   
catch {  
  ...  
}  
finally {  
  ...  
}

catch子句包含用于指定当代码块中抛出异常时应执行的操作的代码try。如果try代码块执行失败并抛出异常,catch则会执行代码块中的代码。如果代码块中的所有代码都已执行完毕且未抛出任何异常,则会跳过代码块try中的代码。catch

finally代码块会在代码try块本身或整个catch代码块执行完毕后执行。无论是否抛出异常,它都会执行。

try代码块可以相互嵌套。如果内部try代码块没有捕获到异常,而外部代码块包含catch异常捕获块,那么外部代码块将会捕获内部代码块抛出的异常try。例如,如果我们有:

try {  
  try {  
    undefined.prop
  finally {  
    console.log('Inner finally block runs');  
  }  
} catch (error) {  
  console.log('Outer catch block caught:', error);  
}

如果我们运行上面的代码,应该会看到“内部 finally 代码块运行”和“外部 catch 代码块捕获到:TypeError:无法读取未定义对象的属性 'prop'”的日志,这符合预期,因为内部try代码块没有捕获到异常,catch所以由外部catch代码块捕获。我们可以看到,内部 finally 代码块在外部 catch 代码块之前执行。代码try...catch...finally是按顺序执行的,所以先添加的代码会在后添加的代码之前执行。

catch我们目前编写的代码块都是无条件的。这意味着它们会捕获所有抛出的异常。该对象error保存着关于抛出异常的数据。它只保存代码catch块内部的数据。如果我们想在代码块外部保留这些数据,则必须将其赋值给catch代码块外部的一个变量。catch代码块执行完毕后,该error对象将不再可用。

finally子句包含在代码try块或catch代码块执行之后、但在代码块下方的语句执行之前会被忽略的语句try...catch...finally。无论是否抛出异常,该子句都会执行。如果抛出异常,即使没有代码块捕获并处理该异常,该子句中的语句finally也会执行catch

因此,` finallyfailure` 代码块非常实用,可以让程序在发生错误时优雅地终止。例如,我们可以将一些无论是否抛出异常都会运行的清理代码(例如用于关闭文件读取句柄的代码)放在 `failure` 代码块中。try当 `failure` 代码块中的某行代码在执行过程中抛出异常时try,该代码块中的其余代码不会执行。因此,如果我们预期在 `failure` 代码块中关闭文件句柄try,但在执行关闭文件句柄的代码行之前抛出了异常,那么为了优雅地结束程序,我们应该将该操作放在 ` finallyfailure` 代码块中,以确保文件句柄始终被清理。我们可以将一些无论是否抛出异常都会运行的代码(例如清理代码)放在 `failure` 代码finally块中,这样就无需在 `failure`try和 `failure`catch代码块中重复编写这些代码。例如,我们可以这样写:

openFile();  
try {  
  // tie up a resource  
  writeFile(data);  
}  
finally {  
  closeFile();   
  // always close the resource  
}

上面的代码中,closeFile无论writeFile运行时是否抛出异常,该函数始终都会运行,从而消除了重复代码。

我们可以使用嵌套try代码块,如下面的代码所示:

try {  
  try {  
    throw new Error('error');  
  }  
  finally {  
    console.log('finally runs');  
  }  
}  
catch (ex) {  
  console.error('exception caught', ex.message);  
}

如果我们查看控制台日志,应该会看到“finally runs”出现在“exception caught error”之前。这是因为代码try...catch块中的所有内容都是逐行执行的,即使是嵌套的代码块。如果我们有更多嵌套,例如以下代码:

try {  
  try {  
    throw new Error('error');  
  }  
  finally {  
    console.log('first finally runs');  
  } 

  try {  
    throw new Error('error2');  
  }  
  finally {  
    console.log('second finally runs');  
  }  
}  
catch (ex) {  
  console.error('exception caught', ex.message);  
}

我们看到控制台日志输出与之前相同。这是因为第一个内部try代码块没有捕获到异常,所以异常被传递到外部catch代码块并被其捕获。如果我们想让第二个try代码块运行,则需要catch向第一个try代码块添加一个额外的代码块,如下例所示:

try {  
  try {  
    throw new Error('error');  
  }  
  catch {  
    console.log('first catch block runs');  
  }    
  finally {  
    console.log('first finally runs');  
  } 

  try {  
    throw new Error('error2');  
  }  
  finally {  
    console.log('second finally runs');  
  }  
}  
catch (ex) {  
  console.error('exception caught', ex.message);  
}

现在我们看到按顺序记录了以下消息:“第一个 catch 块运行”、“第一个 finally 块运行”、“第二个 finally 块运行”、“捕获到异常 error2”。这是因为第一个trycatch 块本身包含一个catchcatch 块,所以该行代码引发的异常throw new Error('error')现在被catch第一个内部trycatch 块捕获。而第二个内部trycatch 块没有关联的catchcatch 块,因此error2异常将被外部 catch 块捕获catch

我们还可以重新抛出代码块中捕获到的错误catch。例如,我们可以编写以下代码来实现这一点:

try {  
  try {  
    throw new Error('error');  
  } 
  catch (error) {  
    console.error('error', error.message);  
    throw error;  
  } finally {  
    console.log('finally block is run');  
  }  
} catch (error) {  
  console.error('outer catch block caught', error.message);  
}

如我们所见,如果运行上述代码,则会按顺序记录以下信息:“error error”、“finally block is run”和“outer catch block caught error”。这是因为内部代码catch块记录了 `finally` 语句抛出的异常throw new Error(‘error’),但在 `finally` 语句console.error(‘error’, error.message);执行后,我们又运行了 `finally` 语句throw error;再次抛出该异常。然后内部finally代码块执行,再次抛出的异常被外部代码块捕获,catch并记录了内部代码块中 `finally` 语句error再次抛出的异常throw errorcatch

由于代码是顺序执行的,我们可以在代码块return末尾执行语句try。例如,如果我们想将一个 JSON 字符串解析成一个对象,并且希望在解析传入的字符串时出错(例如,传入的字符串不是有效的 JSON 字符串)时返回一个空对象,那么我们可以编写以下代码:

const parseJSON = (str) => {  
  try {  
    return JSON.parse(str);  
  }  
  catch {  
    return {};  
  }  
}

上面的代码会JSON.parse解析字符串,如果它不是有效的 JSON,则会抛出异常。如果抛出异常,catch则会调用相应的子句返回一个空对象。如果JSON.parse解析成功,则会返回已解析的 JSON 对象。所以,如果我们运行:

console.log(parseJSON(undefined));  
console.log(parseJSON('{"a": 1}'))

然后我们在第一行得到一个空对象,然后{a: 1}在第二行得到。

异步代码中的 try 块

使用 `return`async和 `return` await,我们可以简化 Promise 代码。在使用 `return` 之前asyncawait我们需要使用then`return` 函数,并将回调函数作为参数传递给所有then函数。如果 Promise 数量很多,这会导致代码冗长。现在,我们可以使用 `return`async和 ` awaitreturn` 语法来替换 `return`then及其关联的回调函数,如下所示。使用 `return`async和 ` awaitreturn` 语法来链式调用 Promise,我们还可以使用`return`try和 `return`catch代码块来捕获被拒绝的 Promise 并优雅地处理它们。例如,如果我们想使用 `return`catch代码块捕获 Promise 拒绝,我们可以这样做:

(async () => {  
  try {  
    await new Promise((resolve, reject) => {  
      reject('error')  
    })  
  } 
  catch (error) {  
    console.log(error);  
  }
})();

在上面的代码中,由于我们拒绝了在try代码块中定义的 Promise,catch代码块捕获了 Promise 的拒绝并记录了错误。因此,运行上面的代码时,我们应该会看到日志输出“error”。虽然它看起来像一个普通的try...catch代码块,但实际上并非如此,因为这是一个async函数。async函数只能返回 Promise,所以我们不能在try...catch代码块中返回 Promise 以外的任何值。函数catch中的代码块async只是该catch函数的简写形式,它链接到 then 函数。因此,上面的代码实际上等价于:

(() => {  
  new Promise((resolve, reject) => {  
      reject('error')  
    })  
    .catch(error => console.log(error))  
})()

async我们看到,当运行上述函数时,会得到相同的控制台日志输出。

该代码块也可以在函数中finally使用。例如,我们可以这样写:try...catchasync

(async () => {  
  try {  
    await new Promise((resolve, reject) => {  
      reject('error')  
    })  
  } catch (error) {  
    console.log(error);  
  } finally {  
    console.log('finally is run');  
  }  
})();

在上面的代码中,由于我们拒绝了在代码块中定义的 Promise try,该catch代码块捕获了 Promise 的拒绝并记录了错误。因此,运行上面的代码时,我们应该看到日志记录了“error”。代码块finally执行完毕后,我们会看到日志记录了“finally is run”。函数finally中的代码块async等同于将finally函数链接到 Promise 的末尾,因此上面的代码等效于:

(() => {  
  new Promise((resolve, reject) => {  
      reject('error')  
    })  
    .catch(error => console.log(error))  
    .finally(() => console.log('finally is run'))  
})()

async我们看到,当运行上述函数时,会得到相同的控制台日志输出。

try...catch我们上面提到的嵌套规则仍然适用于async函数,所以我们可以这样写:

(async () => {  
  try {  
    await new Promise((resolve, reject) => {  
      reject('outer error')  
    })  
    try {  
      await new Promise((resolve, reject) => {  
        reject('inner error')  
      })  
    } 
    catch (error) {  
      console.log(error);  
    } 
    finally { }  
  } 
  catch (error) {  
    console.log(error);  
  } 
  finally {  
    console.log('finally is run');  
  }  
})();

这样我们就可以轻松地嵌套 Promise 并相应地处理它们的错误。这比之前使用链式调用then`and`catch和`functions`函数的方式更简洁。finallyasync

为了处理 JavaScript 程序中的错误,我们可以使用 ` try...catch...finallyif` 代码块来捕获错误。这可以用于同步代码或异步代码。我们将可能抛出异常的代码放在 `if`try代码块中,然后将处理异常的代码放在 `if`catch代码块中。在 `if`finally代码块中,无论是否抛出异常,都会执行所有需要运行的代码。async函数也可以使用try...catch`if` 代码块,但它们像其他async函数一样只能返回 Promise,而try...catch...finally普通函数中的 `if` 代码块可以返回任何类型的值。

文章来源:https://dev.to/aumayeung/handling-exceptions-in-javascript-482p