Tian Jiale's Blog

如何优雅地实现 async/await 的错误处理

异步编程

该部分可以参考 JavaScript 中的异步编程,其中较为详细地介绍了各种异步编程的实现方案的优缺点,为了方便开发我使用了 async/await 方式去解决异步编程的问题。

错误处理

在上面文章中 async/await 的方案中无法实现 Promise 的错误处理,该问题是在处理项目 网络书签 时遇到的,原始代码如下:

exports.main = async (event) => {
  const client = createClient(event.webdavurl, {
    username: event.username,
    password: event.password,
  });
  // 获取文件内容
  const buff = await client.getFileContents(event.path);
  // 将文件内容从二进制转换成字符串
  const decoder = new util.TextDecoder();
  const xmldata = decoder.decode(buff);
  // 解析文件内容
  const parser = new xml2js.Parser();
  const bookmark = await parser.parseStringPromise(xmldata);
  return { xbel: bookmark.xbel };
};

如上可见,async/await 方式中我并没有添加任何错误处理程序,在这两天开始完善代码的时候,尝试过用 Promise.then().catch() 的方法去实现,但不能完美地定位某个错误(增加的那个 if 多丑啊),而且代码量明显增加,代码如下:

exports.main = async (event) => {
  const client = createClient(event.webdavurl, {
    username: event.username,
    password: event.password,
  });
  const result = await client
    .getFileContents(event.path)
    .then((buff) => {
      // 将文件内容从二进制转换成字符串
      const decoder = new util.TextDecoder();
      const xmldata = decoder.decode(buff);
      // 解析文件内容
      const parser = new xml2js.Parser();
      return parser.parseStringPromise(xmldata);
    })
    .then((bookmark) => ({
      xbel: bookmark.xbel,
    }))
    .catch((e) => {
      if (e.message === 'Invalid response: 401 Unauthorized') {
        return Error('配置信息错误!');
      }
      return Error('文件解析错误!');
    });
  if (result instanceof Error) {
    return {
      error: result.message,
    };
  }
  return result;
};

在有使用 try…catch 代码的想法的时候察觉到了有那么点违和感,所以在网上搜索得知了如下处理方式。

await-to

await-to 的思想很简单,其中借鉴了 Go 的错误处理方法,对 Promise 进行包装。

Go 的错误处理方法如下:

data, err := db.Query("SELECT ...")
if err != nil { return err }

很容易地就想到我们可以采用类似的方式去解决 async/await 的错误。

创建一个包装函数如下:

function to(promise) {
  return promise
    .then((data) => {
      return [null, data];
    })
    .catch((err) => [err]);
}

上面的代码可以改成:

exports.main = async (event) => {
  const client = createClient(
    event.webdavurl,
    {
      username: event.username,
      password: event.password,
    },
  );
  // 获取文件内容
  const [err1, buff] = await to(client.getFileContents(event.path));
  if (err1) return { error: '配置信息错误!' };
  // 将文件内容从二进制转换成字符串
  const decoder = new util.TextDecoder();
  const xmldata = decoder.decode(buff);
  // 解析文件内容
  const parser = new xml2js.Parser();
  const [err2, bookmark] = await to(parser.parseStringPromise(xmldata));
  if (err2) return { error: '文件解析错误!' };
  return { xbel: bookmark.xbel };

通过这种方式我们的错误处理更加简洁和易读,想要了解更多可以参考 How to write async await without try-catch blocks in Javascriptawait-to-js