阅读 3592

【译】Async-Await≈Generators+Promises

Async-Await ≈ Generators + Promises

这篇文章我将介绍ES2017async函数为什么是ES2016GeneratorsPromises特性功能的语法糖。

阅读须知

  • 本文不对三者概念进行介绍和讲解
  • 本文唯一的目的就是介绍如何利用GeneratorsPromises去实现async
  • 本文对async和其他相似实现不进行优劣评价
  • 本文代码都是经过巧妙设计以便于理解,他们不适用于实际开发

为什么?

既然async函数已被原生支持,还有理解它工作原理的必要吗?

呃,除了因为好奇它的原理之外,更重要的是为了去支持旧的运行平台。如果你希望使用了新功能的代码可以运行在旧的浏览器版本和Node.js版本,你可能需要使用诸如Babel这样的工具去转换这些新特性。

因此,深刻理解async函数如何被分解成generatorspromises后,在你阅读或调试转换后的代码能派上很大用场。比如,这是一个简单的async函数:

Babel转换成ES2016代码如下(不用完全看懂,下文会解释):

两者差异很大!当然,如果你理解了async的工作原理,那么这段转换之后的代码对你来说也是小菜一碟。

另一个有趣的事实是,浏览器也会将async函数进行实现:浏览器像Babel一样利用generatorspromises转换async

那么到底发生了些什么?

有些时候,为了理解一些东西如何运作,最好的方法就是自己动手做。

比如我们有一段使用了async函数的代码片段,我们如何利用generatorspromises去重写它呢?

这是我们的async函数:

函数体中依次执行三个异步任务,每个任务依赖前一个任务的完成。最后,函数返回最后一个任务的结果。

如何使用generators重写

生成器的功能是:可以退出并再次进入。让我们快速回顾一下它的工作方式,以下是一个简单的generator函数:

这个生成器函数gen拥有一些有趣的特性(从MDN摘取):

  1. 当一个generator函数被调用,函数体内代码并不立即执行。它返回一个遵循了迭代器协议迭代器对象:它有next方法
  2. 执行gen函数体内代码的唯一方法就是在返回的迭代器对象上调用next方法。每一次调用next,函数体内代码就执行到一个yield表达式处,这个表达式的右值赋值给迭代器
  3. next方法也可以接受参数,使用参数调用将会用参数值替换上一条yield表达式的左值,然后执行并返回当前yield表达式的右值
const a = yiled foo();
//    |           |
//    |           |
//   左值         右值
复制代码

请反复理解上述步骤或者参考MDN文档

这些特性如何帮助我们?

到目前为止,你可能会疑惑,generator函数如何表达本文意图?

我们需要建立一个异步工作流模型:即我们需要进行下一步时,必须等待特定任务结束。

但是到目前为止,我们讨论的东西都是同步的。怎么办?

译者注:上文的yield表达式后面全是同步值

关键点是生成器函数可以对promises进行yield

一个generator函数可以对promise进行yield,并且它的迭代器可以被控制停止并等待promise最终resolvereject并对他们决议的值进行下一步处理。这种构造一个可yield promises的迭代器的模式可以满足我们的需求:

Notice how this generator function resembles our async function!

目前为止我们只进行到一半。我们需要一个执行函数体内容的方法,我们需要一个可以控制generator函数迭代器的函数,它能够停止并等待每一个yield promise决议的结果。听上去很复杂,但是实现起来还是很简单的 :

A function that executes a generator function. (Only for explanation, do not use it !)

现在我们可以像下面一样去使用runner函数执行我们的生成器函数init

Use `runner` to execute the body of `init`.

就这么简单!runner函数和init函数的组合使用达到了原生async函数的效果。

请切记这个runner函数仅仅是为了讲解本文意图而做的演示代码,它不适合实际开发场景,如果你需要一个合适的实现,你可以在这里找找。

总结

我们开始于一个async函数,然后利用generatorspromises去实现相同功能:

深入实践

  • 本文伊始,我们看到BabelES2017async函数转换之后,如何利用ES2016generatorspromises去实现。你可以回顾一下之前转换之后的_asyncToGenerator函数,比较我们的runner函数就会发现两者很相似。实际上,_asyncToGenerator函数是我们这里极其简单的runner函数万无一失的版本

  • 如果你还有兴趣,你可以进行下一步研究,即把async函数转换成没有generatorsES2015版本代码。这样你可能需要去模拟generators本身(参见regenerator project

我希望通过这篇文章拨开async函数的迷雾,他提供了简单的语法,减少了代码噪声。async函数的提议是这样描述的:

The introduction of Promises and Generators in ECMAScript presents an opportunity to dramatically improve the language-level model for writing asynchronous code in ECMAScript.

感谢Akos, Alisa以及Kristian为完善这篇文章所提供的反馈。

文章分类
前端