在JavaScript的async function里应该如何throw errors

在JavaScript的async function里应该如何throw errors

什么是 async function呢?按照MDN的解释,这是一种通过Promise来是书写异步调用更清晰的方式。
async关键字表示出一个function是不是async function,使得这个function总是会执行Promise的resolved或者rejected。就是说即使我们在async function里throw errors,外部也捕获不到,而只会执行rejected部分的代码。

常规的throw error和 try catch流程

假设我们有一个upperCase方法,接受的参数必须是字符串,否则报错

function upperCase(name) {
  if (typeof name !== "string") {
    throw TypeError("name must be a string");
  }
  return name.toUpperCase();
}

module.exports = upperCase;

那么我们的Jest单元测代码可能是这样的:

"use strict";

const assert = require("assert");
const upperCase = require("../function");

describe("upperCase function", () => {
  test("it throws when name is not provided", () => {
    assert.throws(() => upperCase());
  });
  test("it throws when name is not a string", () => {
    assert.throws(() => upperCase(9));
  });
});

在类里也是一样,假设我们有个Person类,构造函数接受的name参数必须是字符串,否则抛出异常

class Person {
  constructor(name) {
    if (typeof name !== "string") {
      throw TypeError("name must be a string");
    }

    this.name = name;

  }

  // some method here
}

module.exports = Person;

那么我们的Jest测试代码还是类似:

"use strict";

const assert = require("assert");
const Person = require("../index");

describe("Person class", () => {
  test("it throws when name is not provided", () => {
    assert.throws(() => new Person());
  });
  test("it throws when name is not a string", () => {
    assert.throws(() => new Person(9));
  });
});

测试一个async function

我们给Person类添加一个async getData(url)方法,按照同样的方式throw error,这个流程就不工作了!

class Person {
  constructor(name) {
    if (typeof name !== "string") {
      throw TypeError("name must be a string");
    }

    this.name = name;
  }

  async getData(url) {
    if (typeof url !== "string") {
      throw TypeError("url must be a string");
    }
    // const response = await fetch(url)
    // do stuff
  }
}

module.exports = Person;

我们如果这样写Jest测试方法捕获不到期望的异常。

"use strict";

const assert = require("assert");
const Person = require("../index");

describe("Person methods", () => {
  test("it throws when url is not a string", () => {
    const valentinogagliardi = new Person("valentinogagliardi");
    assert.throws(() => valentinogagliardi.getData());
  });
});

得到的结果会是这样:

 FAIL  test/index.test.js
   Person methods › it throws when url is not a string

    assert.throws(function)

    Expected the function to throw an error.
    But it didn't throw anything.

    Message:
      Missing expected exception.

这是因为这时,我们在getData()里throw的error,会以rejected.catch()方式被捕获,而不是通常的try catch()方式。
我们把测试代码改成下面这样就可以了:

"use strict";

const assert = require("assert");
const Person = require("../index");

describe("Person methods", () => {
  test("it rejects when url is not a string", async () => {
    expect.assertions(1);
    const valentinogagliardi = new Person("valentinogagliardi");
    await expect(valentinogagliardi.getData()).rejects.toEqual(
      TypeError("url must be a string")
    );
  });
});

业务代码中对应的的catch就是这样:

const Person = require("../index");

const valentinogagliardi = new Person("valentinogagliardi");
valentinogagliardi
  .getData()
  .then(res => res)
  .catch(err => console.error(err));

而下面使用try catch()的业务代码是不会catch这相应的异常的:

const Person = require("../index");

async function whatever() {
  try {
    const valentinogagliardi = new Person("valentinogagliardi");
    await valentinogagliardi.getData();
    // do stuff with the eventual result and return something
  } catch (error) {
    throw Error(error);
  }
}

whatever();

简单的小结一下:

  • 在async function里不会以常规方式抛出异常
  • async function总是返回Promise,会是resolved或者rejected
  • 要捕获异常必须使用Promise的.catch()方法
  • 在Jest单元测试中
    • assert.throws只能测试常规方式的异常
    • async function的异常,需要通过expectrejects的方式捕获

参考

leon

每天进步一点点