Async Function

Async Function,即异步函数,为异步编程带来了非常大的提升,瞬间把开发效率和维护效率提升了一个数量级。

它的用法非常简单,只要用 async 关键字声明一个函数为“异步函数”,这个函数的内部就可以使用 await 关键字,让其中的语句等待异步执行的结果,然后再继续执行。我们还是用代码来说话吧:

function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

async function f1() {
  var x = await resolveAfter2Seconds(10);
  console.log(x);
}
f1();

// 输出:
// (2秒后)10

这段代码来自 MDN。里面先声明了 resolveAfter2Seconds 函数,执行它会返回一个 Promise 对象,等待2秒钟之后完成。然后声明了异步函数 f1,里面只有两行代码,第一行用 await 表示要等后面的 Promise 完成,再进行下一步;于是2秒之后,输出了“10”。

这段代码比较短,不太能体现异步函数的价值,我们改写一下查找最大文件的代码试试看:

const fs = require('./FileSystem');

async function findLargest(dir) {
  let files = await fs.readdir(dir);
  let stats = [];
  for (let i = 0, len = files.length; i < len; i++) {
    stats.push(await fs.stat(files[i]));
  }
  return stats
    .filter( stat => stat.isFile() )
    .reduce( (memo, stat) => {
      if (memo.size > stat.size) return memo;
      return stat;
    });
}

findLargest('path/to/dir')
  .then(file => {
    console.log(file);
  });

怎么样,是不是异常清晰明了,连代码量都减少了很多。而且,因为每次 await 都会等待后面的异步函数完成,所以我们还无意间写出了生成队列的代码。当然你可能不希望要队列,毕竟都异步了,队列的吸引力实在不大,那么我们只需要把 for 循环改成下面这个样子就行了:

let stats = await Promise.all(files.map( file => fs.stat(file) ));

酷不酷!想不想学?

设计

见识过异步函数的强大之后,我们来捋一捋一下它的具体设计。

异步函数主要由两部分组成:

async

声明一个函数是异步函数。执行异步函数会返回一个 Promise 实例。异步函数的返回值会继续向后传递。

async 的语法比较简单,就在普通函数前面加个 async 而已。它也支持箭头函数,也可以用作立刻执行函数。

await

await 操作符只能用在异步函数的内部。表示等待一个 Promise 完成,然后返回其返回值。语法是这样的:

[return_value] = await expression;
  • return_value 返回值:Promise 执行器的返回值
  • expression 表达式:Promise 对象,其它类型会使用 Promise.resolve() 转换

执行到 await 之后,异步函数会暂停执行,等待表达式里的 Promise 完成。resolve 之后,返回 Promise 执行器的返回值,然后继续执行。如果 Promise 被 rejected,就抛出异常。

捕获错误

异步函数最大的改进其实就在捕获错误这里。它可以直接使用 try/catch/throw 就像我们之前那样。

比如,一款前后端分离的应用,论坛,需要用户登录。所以在初始化的时候就会向服务器请求用户信息,如果返回了,就继续加载用户关注列表等其它数据;如果返回 401 错误,就让用户登录。用 Promise 的话,我们可能会这样做:

function getMyProfile() {
  return fetch('api/me')
    .then(response => {
      if (response.ok) {
        return response.json();
      }
    })
    .then(profile => {
      global.profile = profile
    })
    .catch( err => {
      if (err.statusCode === 401) {
        location.href = '#/login';
      } else {
        // 处理其它错误
      }
    });
}

这样做也不是不可以,但是如果业务逻辑越来越复杂,队列也会越来越长,也许每一步都需要处理错误,那么队列就会变得难以维护。

如果用异步函数就会简单很多:

async function getMyProfile() {
  let profile;
  try {
    global.profile = await fetch('api/me');
  } catch (e) {
    if (e.statusCode === 401) {
      location.href = '#/login';
    } else {
      // 处理其它错误
    }
  }
}

另外,如果你真的捕获到错误,你会发现,它的堆栈信息是连续的,甚至可以回溯到调用异步函数的语句,继而可以审查所有堆栈里的变量。这对调试程序带来的帮助非常巨大。(需要运行时支持,转译的不行。)

普及率

Async Function 的普及率 截图于:2017-06-23

可以看出,大部分主流浏览器都已经支持异步函数,值得关注的,只有 iOS 10.2 和 Android 4。当然在国内的话,IE 始终是老大难问题……

results matching ""

    No results matching ""