promise
解决了“回调地狱”的问题,使异步回调的代码书写由横向变为纵向,但是一堆then
的语义性不够好。
协程
yield
命令。执行到该命令时该协程的执行权交给另一个协程,一段时间后执行权交回时才继续执行。
优点是写法很像同步操作的代码。
Generator函数
协程在ES6中的实现就是Generator
函数。
不同于普通函数的地方:
- 函数可以暂停执行,用
yield
命令。 - 调用Generator函数会返回内部指针对象(遍历器),其具有的
next
方法可以控制Generator函数的继续执行。
|
|
这里有个小注意点,value
为undefined
的前一步next
执行结果中,done
一定为false
吗?看下面这个例子:
|
|
如果把上例中的yield 'ending'
改成return 'ending'
,则下面第三个next
方法执行后会返回{ value: 'ending', done: true }
。
也就是说,如果Generator函数最后是return
语句,则执行完return
语句后,遍历器的done
就是true
了;如果没有return
语句,则执行完最后一步yield
命令的表达式之后,遍历器的done
会是false
,需要再执行一次next
才会变为false
.
给next
方法传的参数,会作为上个阶段异步任务的返回结果,被函数体内return
的变量接收。因此,传参执行next
返回的遍历器的value
就是这个返回的变量,也即传入的参数?那传参的意义是什么?
Generator函数内部也可以用try...catch
捕获函数体外抛出的错误,在函数体外抛出错误g.throw('error occurred!')
,在函数体内捕获错误进行处理,实现异步编程中出错代码与处理错误的代码的时间、空间上的分离。
Generator函数的自动执行
Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。两种方法可以做到这一点:
(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。
用Thunk函数实现Generator函数的自动执行
传值调用:传入计算好的参数值
传名调用:传入参数表达式,函数体内只在需要参数值的时候才计算该表达式。
Thunk函数是传名调用的实现,将参数表达式放在临时函数中,将这个临时函数名传入。需要时执行这个临时函数。该临时函数就是Thunk函数。
JavaScript中的Thunk函数:将多参数函数(参数中必须含有回调函数),替换成单参数版本,切只接受回调函数作为参数。
生产环境使用Thunkify模块作为转换器。Thunkify源码
Thunk函数可用于Generator函数的自动流程管理:在回调函数中将执行权交还给Genenrator函数。
手动执行:
|
|
用Thunk函数自动执行:Generator函数中的yield命令后面必须是Thunk函数。
|
|
用Promise实现Generator函数的自动执行
co
函数库返回一个Promise
对象。Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。
|
|
主要是递归执行gen.next()
,每一步执行后返回的值的value
(ret.value
)都用一个promise包裹起来,然后在这个promise的then
函数中的回调函数中继续执行gen.next()
co支持并发的异步操作,用数组或对象存储这些兵法操作,等它们全部完成之后才进行下一步。
async函数
“异步编程的最高境界,就是根本不用关心它是不是异步。”
async函数其实是Generator函数的语法糖。async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
与Generator函数相比,async函数有如下优点:
- 内置执行器:Generator函数的执行必须依靠执行器。
- 更好的语义:async - 函数内部有异步操作,await - 紧跟在后面的表达式需要等待结果。
- 更广的适用性:Genertor函数使用自动执行器时,
yield
后面要跟Thunk函数或者Promise对象;await
后面可以跟Promise对象(一般是个函数,执行该函数返回的Promise对象)或原始类型值(后者等于同步操作)。
async
函数返回一个Promise对象。
一般用try...catch
将await
包裹起来。
await
命令只能用在async
函数中。
(1)多个请求并发执行:不能用forEach
对每个请求await
,而应使用Promise.all
:
|
|
(2)多个请求继发执行:用for
循环:
|
|
补充Generator函数语法
next
方法的参数表示上一个yield
表达式的值。也即:next()
是将yield
表达式替换成一个值。
第一次调用next
方法时不用传参。传了也会被V8引擎忽略。
使用for...of
语句时不需要next
方法:
|
|
|
|
next()
, throw()
, return()
都是对yield
语句后面表达式的替换。
yield*
yield*
后面的 Generator 函数(没有return
语句时),不过是for...of
的一种简写形式,完全可以用后者替代前者。反之,在有return
语句时,则需要用var value = yield* iterator
的形式获取return
语句的值。
用yield*
取出嵌套数组的所有成员:
|
|
用yield*
遍历完全二叉树:
|
|
Generator
与状态机
|
|
用Generator函数改写如下:不需要用外部变量来保存状态(因为),更安全。
|
|