JavaScript 异步方案 async/await

来源:互联网 发布:知乎阿波罗和雅典娜 编辑:程序博客网 时间:2024/05/21 09:22

构建一个应用程序总是会面对异步调用,不论是在 Web 前端界面,还是 Node.js 服务端都是如此,JavaScript 里面处理异步调用一直是非常恶心的一件事情。以前只能通过回调函数,后来渐渐又演化出来很多方案,最后 Promise 以简单、易用、兼容性好取胜,但是仍然有非常多的问题。其实 JavaScript 一直想在语言层面彻底解决这个问题,在 ES6 中就已经支持原生的 Promise,还引入了 Generator 函数,终于在 ES7 中决定支持 async 和 await。

基本语法

async/await 究竟是怎么解决异步调用的写法呢?简单来说,就是将异步操作用同步的写法来写。先来看下最基本的语法(ES7 代码片段):

const f = () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      resolve(123);    }, 2000);  });};const testAsync = async () => {  const t = await f();  console.log(t);};testAsync();

首先定义了一个函数 f ,这个函数返回一个 Promise,并且会延时 2 秒, resolve 并且传入值 123。 testAsync 函数在定义时使用了关键字 async ,然后函数体中配合使用了 await ,最后执行 testAsync 。整个程序会在 2 秒后输出 123,也就是说 testAsync 中常量 t 取得了 f 中 resolve 的值,并且通过 await 阻塞了后面代码的执行,直到 f 这个异步函数执行完。

对比 Promise

仅仅是一个简单的调用,就已经能够看出来 async/await 的强大,写码时可以非常优雅地处理异步函数,彻底告别回调恶梦和无数的 then 方法。我们再来看下与 Promise 的对比,同样的代码,如果完全使用 Promise 会有什么问题呢?

const f = () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      resolve(123);    }, 2000);  });};const testAsync = () => {  f().then((t) => {    console.log(t);  });};testAsync();

从代码片段中不难看出 Promise 没有解决好的事情,比如要有很多的 then 方法,整块代码会充满 Promise 的方法,而不是业务逻辑本身,而且每一个 then 方法内部是一个独立的作用域,要是想共享数据,就要将部分数据暴露在最外层,在 then 内部赋值一次。虽然如此,Promise 对于异步操作的封装还是非常不错的,所以 async/await 是基于 Promise 的, await 后面是要接收一个 Promise 实例。

对比 RxJS

RxJS 也是非常有意思的东西,用来处理异步操作,它更能处理基于流的数据操作。举个例子,比如在 Angular2 中 http 请求返回的就是一个 RxJS 构造的 Observable Object,我们就可以这样做:

$http.get(url)  .map(function(value) {    return value + 1;  })  .filter(function(value) {    return value !== null;  })  .forEach(function(value) {    console.log(value);  })  .subscribe(function(value) {    console.log('do something.');  }, function(err) {    console.log(err);  });

如果是 ES6 代码可以进一步简洁:

$http.get(url)   .map(value => value + 1)   .filter(value => value !== null)   .forEach(value => console.log(value))   .subscribe((value) => {     console.log('do something.');   }, (err) => {     console.log(err);   });

可以看出 RxJS 对于这类数据可以做一种类似流式的处理,也是非常优雅,而且 RxJS 强大之处在于你还可以对数据做取消、监听、节流等等的操作,这里不一一举例了,感兴趣的话可以去看下 RxJS 的 API。

这里要说明一下的就是 RxJS 和 async/await 一起用也是可以的,Observable Object 中有 toPromise 方法,可以返回一个 Promise Object,同样可以结合 await 使用。当然你也可以只使用 async/await 配合 underscore 或者其他库,也能实现很优雅的效果。总之,RxJS 与 async/await 不冲突。

异常处理

通过使用 async/await,我们就可以配合 try/catch 来捕获异步操作过程中的问题,包括 Promise 中 reject 的数据。

const f = () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      reject(234);    }, 2000);  });};const testAsync = async () => {  try {    const t = await f();    console.log(t);  } catch (err) {    console.log(err);  }};testAsync();

代码片段中将 f 方法中的 resolve 改为 reject ,在 testAsync 中,通过 catch 可以捕获到 reject 的数据,输出 err 的值为 234。 try/catch 使用时也要注意范围和层级。如果 try 范围内包含多个 await ,那么 catch 会返回第一个 reject 的值或错误。

const f1 = () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      reject(111);    }, 2000);  });};const f2 = () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      reject(222);    }, 3000);  });};const testAsync = async () => {  try {    const t1 = await f1();    console.log(t1);    const t2 = await f2();    console.log(t2);  } catch (err) {    console.log(err);  }};testAsync();

如代码片段所示, testAsync 函数体中 try 有两个 await 函数,而且都分别 reject ,那么 catch 中仅会触发 f1 的 reject ,输出的 err 值是 111。