跳到主要内容

理解 JavaScript async/await

· 阅读需 5 分钟

虽然Promise解决了callback hell的问题,但遇到复杂业务的时候可能会充斥着大量的 then函数。

故 es7 引入了async/await, 这个被称作终极的异步解决方案,其实是Generator的语法糖

Generator

Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个迭代器对象,可以依次遍历 Generator 函数内部的每一个状态。但只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

function* helloWorldGenerator() {
yield 'hello'
yield 'world'
return 'ending'
}

var hw = helloWorldGenerator()
hw.next() // { value: 'hello', done: false }
hw.next() // { value: 'world', done: false }
hw.next() // { value: 'ending', done: true }
hw.next() // { value: undefined, done: true }

Generator函数被调用时并不会执行,只有当调用next方法、内部指针指向该语句时才会执行,即函数可以暂停,也可以恢复执行。每次调用遍历器对象的next方法,就会返回一个有着 valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

Generator 函数暂停恢复执行原理

一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。

协程是一种比线程更加轻量级的存在。普通线程是抢先式的,会争夺 cpu 资源,而协程是合作的,可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。它的运行流程大致如下:

  • 协程 A 开始执行
  • 协程 A 执行到某个阶段,进入暂停,执行权转移到协程 B
  • 协程 B 执行完成或暂停,将执行权交还 A
  • 协程 A 恢复执行

执行器

通常,我们把执行生成器的代码封装成一个函数,并把这个执行生成器代码的函数称为执行器, co 模块就是一个著名的执行器

Generator 是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。两种方法可以做到这一点:

  1. 回调函数。将异步操作包装成 thunk 函数,在回调函数里面交回执行权。
  2. Promise 对象。将异步操作包装成 Promise 对象,用 then方法交回执行权。

一个基于 Promise 对象的简单自动执行器:

function run(gen) {
var g = gen()

function next(data) {
var result = g.next(data)
if (result.done) return result.value
result.value.then(function (data) {
next(data)
})
}
next()
}

可以这样使用

function* foo() {
let response1 = yield fetch('<https://xxx>') //返回promise对象
console.log('response1')
console.log(response1)
let response2 = yield fetch('<https://xxx>') //返回promise对象
console.log('response2')
console.log(response2)
}
run(foo)