ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。
Generator 有两个区分于普通函数的部分:
- 在 function 后面,函数名之前有个 *
- 函数内部有 yield 表达式
基本使用
function * foo() {
console.log('t1')
}
// 函数调用会返回一个遍历器对象
const ga = foo()
此时,执行node generator.js,得不到任何结果
function * foo() {
console.log('t1')
}
// 函数调用会返回一个遍历器对象
const ga = foo()
ga.next()
多写一句ga.next(),再执行node generator.js,就能打印出t1来了
yield关键字
function * foo() {
console.log('t1')
yield 'yyyyy'
console.log('t2')
}
const ga = foo()
ga.next()
执行node generator.js,只打印出了t1,没打印出t2,这是为什么呢?因为我们在使用遍历器对象ga去调用next方法时,最终只会执行到yield的位置。后面的代码是不会被执行到的
因此,yield被称为“暂停”
遍历器最大的特点,就是让函数能有一个“暂停”的功能
想要让t2被打印出来怎么办?
在ga.next()后面再写一句ga.next()
ga.next()的返回值
ga就是遍历器对象
function * foo() {
console.log('t1')
yield 'yyyyy'
console.log('t2')
}
const ga = foo()
const yd = ga.next()
console.log(yd)
打印出
t1
{ value: ‘yyyyy’, done: false }
value就是前面执行yield的结果
done为false,表示foo有没有执行完(这里还有一句console.log(‘t2’)没执行,需要你再写一句ga.next()才能执行。但如果你把ga.next()写到console.log(yd)后面,那么执行到yd这一句时,也会打印出done:false)
const ga = foo()
const yd = ga.next()
console.log(yd)
const yd2 = ga.next()
console.log(yd2)
打印出
t1
{ value: ‘yyyyy’, done: false }
t2
{ value: undefined, done: true }
ga.next()的参数
function * foo() {
console.log('t1')
const v = yield 'yyyyy'
console.log(v)
console.log('t2')
}
const ga = foo()
const yd = ga.next()
console.log(yd)
const yd2 = ga.next('444')
console.log(yd2)
我们给ga.next()传参的目的,就是下一次调用next()时,可以在yield那里接收返回值。在上面的代码中,第一个ga.next()执行的是yield右边的内容(‘yyyyy’),第二个ga.next()会从yield的左边开始执行。若yield左边没有代码,就从yield的下一行代码开始执行
打印出
t1
{ value: ‘yyyyy’, done: false }
444
t2
{ value: undefined, done: true }
Generator的作用
通过以上分析,可以发现Generator的最大作用是“暂停”。我们可以让异步代码执行时,若还没得到结果,就先暂停下来
先引入封装好的myAjax方法
<script src="./9.promiseAjax.js"></script>
再写js逻辑
function * foo() {
yield myAjax('https://jsonplaceholder.typicode.com/comments?id=2')
yield myAjax('https://jsonplaceholder.typicode.com/comments?id=3')
}
var ga = foo()
var yd1 = ga.next()
console.log(yd1)
// 打印出的yd1为 { value: Promise(一堆内容), done: false }
接下来就可以通过Promise实例对象的.then方法,得到第一个ajax请求返回的响应结果。然后再调用ga.next(),再执行foo()里面的第二个ajax请求,
yd1.value.then( data => {
console.log(data)
var yd1 = ga.next()
yd1.value.then( data => {
console.log(data)
})
})
若有很多很多个yield ajax…,就需要用递归做封装了
function co(foo) {
var ga = foo()
function handler(res) {
if (res.done) return
res.value.then( data => {
console.log(data)
handler(ga.next())
})
}
handler(ga.next())
}
co(generatorAjax)
generatorAjax的逻辑这么来写:
function * generatorAjax() {
yield myAjax('https://jsonplaceholder.typicode.com/comments?id=2')
yield myAjax('https://jsonplaceholder.typicode.com/comments?id=3')
}
是不是有点像 async await的写法
async function generatorAjax() {
await myAjax('https://jsonplaceholder.typicode.com/comments?id=2')
await myAjax('https://jsonplaceholder.typicode.com/comments?id=3')
}
其实async await就是语法糖,内部就是用Generator来实现的