Skip to content

js 中 async 和 await 异常处理 #37

@wangjing013

Description

@wangjing013

简单的 try catch

通常处理错误一般通过 try catch 去进行处理, 下面先来看一个简单的例子

function thisThrows() {
   throw new Error("Thrown from thisThorws()");
}

try {
   thisThrows();
} catch (e) {
   console.log(e); // (1)正常打印
} finally {
   console.log("We do cleanup here"); //(2) We do cleanup here
}

上面按照预期进行打印输出:

output:
Error: Thrown from thisThorws()
We do cleanup here

使用 try catch 处理 Promise Rejecting

下面修改 thisThrows 函数, 如下:

async function thisThrows() {
  throw new Error("Thrown from thisThrows()");
}
try {
  thisThrows();
} catch (error) {
  console.log(e); // 不会打印
} finally {
  console.log("We do cleanup here"); // (1)正常打印
}

上面thisThrows 变成了异步函数, 通过 async 修饰的函数, 其会返回 Promise .

async 函数中有如下几种情况:

  • 当没有显示定义返回语句. 它相当于返回了一个 resolving promise. 等价于 return Promise.Resolve()
  • 当返回一个值, 它将返回 resolving promise 其 resolve 的值为其返回值, 等价于 return Promise.Resolve("My return String")
  • 当抛出一个 error 时, 将基于 error 返回一个 rejected promised. 等价于 return Promise.reject(error)

上面输出如下:

output:
We do cleanup here
Uncaught (in promise) Error: Thrown from thisThrows()

从结果来看并没有被try catch捕获, 为什么? try catch 只能捕获同步到代码.

其实我们有两种方式可以解决上面的问题:

  • 通过 await 的方式去调用 thisThrows
  • 通过 .catch() 方式

第一种方式:

async function thisThrows() {
  throw new Error("Thrown from thisThrows()");
}
async function run() {
  try {
    await thisThrows();
  } catch (error) {
    console.log(error); //(1) 打印
  } finally {
    console.log("We do cleanup here"); // (2)正常打印
  }
}
run();

上面输出如下:

Output:
Error: Thrown from thisThrows()
We do cleanup here

第二种方式:

async function thisThrows() {
  throw new Error("Thrown from thisThrows()");
}
thisThrows()
  .catch(console.error)
  .then(() => console.log("We do cleanup here"));

上面输出如下:

Output:
Error: Thrown from thisThrows()
We do cleanup here

稍微复杂的案例

async function thisThrows() {
  throw new Error("Thrown from thisThrows()");
}
async function myFunctionThatCatches() {
  try {
    return thisThrows();
  } catch (e) {
    console.error(e);
  } finally {
    console.log("We do cleanup here");
  }
  return "Nothing found";
}

async function run() {
  const myValue = await myFunctionThatCatches();
  console.log(myValue);
}

上面从 async 函数中返回 thisThrows, 看看会输出什么?

预期的输出:

We do cleanup here
Nothing found

实际的输出:

We do cleanup here
Uncaught (in promise) Error: Thrown from thisThrows()

为什么?

下面分析下执行步骤:

  • 首先 thisThrows() 是一个 async 方法, 且内部抛出一个错误
  • thisThrows() 被执行时, 它会返回状态为 ``rejected 的 Promise 对象 A.
  • 该 A 对象作为 myFunctionThatCatches 函数的返回值
  • await 发现 A 对象为 rejected 状态,则会向外抛出该错误
  • 同样 console.log(myValue) 并不会执行

同样前面提到 try catch 语句并不能捕获异步错误, 所以其并不会进入 catch 中, 但 finally 不管情况都会被执行.

如果想按照预期的输出, 可以做如下的修改:

async function thisThrows() {
  throw new Error("Thrown from thisThrows()");
}
async function myFunctionThatCatches() {
  try {
    return await thisThrows();
  } catch (e) {
    console.error(e);
  } finally {
    console.log("We do cleanup here");
  }
  return "Nothing found";
}

async function run() {
  const myValue = await myFunctionThatCatches();
  console.log(myValue);
}

上面输出结果为:

Output:
Error: Thrown from thisThrows()
We do clearnup here
Nothing found

重置 error stack trace

有时我们想具化到特定错误,可能会在 catch 中重新返回新的错误对象, 就像下面这样:

async function thisThrows() {
  throw new Error("Thrown from thisThrows()");
}
async function myFunctionThatCatches() {
  try {
    return await thisThrows();
  } catch (e) {
    throw new TypeError(e.message);
  } finally {
    console.log("We do cleanup here");
  }
  return "Nothing found";
}

async function run() {
  const myValue = await myFunctionThatCatches();
  console.log(myValue);
}

上面输出如下:

Output:
VM569:8 Uncaught (in promise) TypeError: Thrown from thisThrows()
at myFunctionThatCatches (<anonymous>:8:17)
at async run (<anonymous>:16:25)

从上面的结果来看, 错误原因没有被修改, 但细心的会发现一个错误栈被重写. 上面看到错误发生是在 myFunctionThatCatches 中, 其实真正错误是在 thisThrows 中.

当我们去重写错误栈的时, 需要去注意.

总结

  • try catch 语句只能捕获同步错误
  • 可以通过 try catch + async 函数处理异步错误
  • 可以通过 .catch() 处理异步错误

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions