1.打破完整运行
我们普遍都会依赖一个假定:一个函数一旦开始执行,就会运行到结束,期间不会有其他代码能够打断它并插入其间。不过ES6引入了一个新的函数类型,他并不符合这种运行到结束的特征,这类新的函数叫做生成器
javascript
function *foo(){
x++
yield //在这里暂停代码
console.log(x);
}
var x = 1
var it = foo()
it.next()
it.next()
上面的代码就是一个生成器foo。声明的方式是*foo,这是为了与普通函数区分开来。
var it = foo()这段代码并没有执行生成器,而是构造了一个迭代器(iterator),通过这个迭代器来控制来控制生成器的运行。
it.next()启动了生成器,代码运行到了第一个yield,即执行了x++这段代码,然后就停止了。
第二个it.next()才执行输出x。
这就是生成器,是一类特殊的函数,可以一次或多次启动和暂停,并不一定非得要完成。
1.输入和输出
生成器任然是一个函数,一些基本的特性没有改变,任然可以接受参数(输入)或者返回值(输出)
javascript
function *foo(x,y){
return x * y
}
var it = foo(6,7)
var res = it.next()
res.value //42
我们将foo(6,7)赋值给迭代器it,尽管看起来很熟悉,但是生成器foo并没有向普通函数一样实际运行。然后调用it.next(),指示生成器foo从当前位置开始继续运行,停在下一个yield处或者直到生成器结束。
这里的next()调用的结果是一个对象,他有一个value属性,持有从*foo()返回的值。换句话说,yield会导致生成器在执行过程中发送出一个值,这有点类似于return
javascript
function *foo(x){
var y = x * (yield 'hello') //yield的第一个值
return y
}
var it = foo(6)
var res = it.next() //第一个next不传入任何东西
res.value //'hello'
res = it.next(7) //向等待的yield传入7
res.value //42
消息的传递是双向的,yield..作为一个表达式可以发出消息响应next()调用,next()调用也可以向暂停的yield表达式发送值。
第一个var res = it.next()遇到yield就会停止运行,并将yield后面的表达式传递给res。
第二个res = it.next(7)会将7传给暂停的yield,此时生成器运行到了最后,因此res的值实际上就是return的值
2.多个迭代器
ini
function *foo(x){
var x = yield 2
z++
var y = yield(x * z)
console.log(x,y,z);
}
var z = 1
var it1 = foo()
var it2 = foo()
var val1 = it1.next().value //2,yield 2
var val2 = it2.next().value //2,yield 2
val1 = it1.next(val2 * 10).value //40,x:20;z:2
val1 = it1.next(val1 * 5).value //600,x:200;z:3
it1.next(val2 / 2) //y:300;20 322 3
it2.next(val1 / 4) //y:10;200 10 3
迭代器仍然是函数,每一个迭代器内部的值不会互相干扰
生成器+Promise,异步迭代生成器
回顾一下Ajax例子中基于Promise的实现方法
javascript
function request(url){
return new Promise((resolve, reject) => {
ajax(url,resolve)
})
}
function foo(x,y){
return request(
`http://some.url.1?x=${x}&y=${y}`
)
}
foo(11,31)
.then(
function(text){
console.log(text);
},
function(err){
console.log(err);
}
)
这里支持Promise的foo函数在发出Ajax调用后返回了一个Promise,这暗示我们可以通过foo构造一个Promise,并且通过生成器把它yield出来,然后通过迭代器控制代码就可以接受到这个Promise。
但是迭代器应该对这个Promise做些什么呢?
他应该侦听这个Promise的决议(完成或拒绝),然后要么使用完成信息恢复生成器运行,要么向生成器抛出一个带有拒绝原因的错误。这就是Promise和生成器的最大效用。
javascript
function request(url){
return new Promise((resolve, reject) => {
ajax(url,resolve)
})
}
function foo(x,y){
return request(
`http://some.url.1?x=${x}&y=${y}`
)
}
function *main(){
try {
var text = yield foo(11,13)
} catch (error) {
console.error(err)
}
}
var it = main()
var p = it.next().value
// 等待Promise决议
p.then(
function(text){
it.next(text)
},
function(err){
it.throw(err)
}
)
我们发现,*main生成器中的代码完全不需要改变,在生成器内部,不管什么值yield出来,都只是一个透明的细节,我们甚至没有意识到其发生。
我们需要关注的额,是怎么运行*main,首先通过var it = main()开启生成器,再通过var p = it.next().value获得foo返回的Promise并赋值给p,此时的p还是未决议状态,一旦Promise完成了,就会调用it.next()或者it.throw()执行生成器下一步。
当然,我们不可能为每一个生成器都编写不同的Promise链,如果有一个工具为我们实现的话该会有多好呀!
aysnc和await
前面的模式--生成器yield出Promise,然后通过控制生成器的迭代器来执行它,直到结束。这种写法想必大家都想到了async和await,是的没错,与其说async和await是Promise的语法糖,我更喜欢说他们是Promise+await是Promise+生成器的语法糖,他们帮我们省略了为每一个迭代器添加Promise链。
如果你await了一个Promise,async函数就会自动获知要做什么,他会暂停这个函数,直到Promise决议(这是异步过程,await后面的代码会放入微任务队列)。