1.概念
-
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
-
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
-
Generator 函数除了状态机,还是一个遍历器对象生成函数。
-
可暂停函数(惰性求值), yield可暂停,next方法可启动。每次返回的是yield后的表达式结果
2.特点
- function关键字与函数名之间有一个星号;
- 函数体内部使用yield表达式,定义不同的内部状态
function* generatorExample(){
console.log("开始执行")
yield 'hello';
yield 'generator';
}
// generatorExample()
// 这种调用方法Generator 函数并不会执行
let MG = generatorExample() // 返回指针对象
MG.next() //开始执行 {value: "hello", done: false}
Generator 函数是分段执行的,调用next方法函数内部逻辑开始执行,遇到yield表达式停止,返回{value: yield后的表达式结果/undefined, done: false/true},再次调用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 }
yield后面接函数
yield后面接函数的话,到了对应暂停点yield,会马上执行此函数,并且该函数的执行返回值,会被当做此暂停点对象的value
function fn(num) {
console.log(num)
return num
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next())
// 1
// { value: 1, done: false }
console.log(g.next())
// 2
// { value: 2, done: false }
console.log(g.next())
// { value: 3, done: true }
yield后面接Promise
function fn(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num)
}, 1000)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.next())
3.next传递参数
generator函数可以用next方法来传参,并且可以通过yield来接收这个参数,注意两点
- 第一次next传参是没用的,只有从第二次开始next传参才有用
- next传值时,要记住顺序是,先右边yield,后左边接收参数
function* gen() {
const num1 = yield 1
console.log(num1)
const num2 = yield 2
console.log(num2)
return 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next(11111))
// 11111
// { value: 2, done: false }
console.log(g.next(22222))
// 22222
// { value: 3, done: true }
Promise+next传参
function fn(nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
console.log(res1) // 1秒后同时输出 2
const next2 = g.next(res1) // 传入上次的res1
next2.value.then(res2 => {
console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
console.log(res2) // 2秒后同时输出 4
const next3 = g.next(res2) // 传入上次的res2
next3.value.then(res3 => {
console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
console.log(res3) // 3秒后同时输出 8
// 传入上次的res3
console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
})
})
})
4.与 Iterator 接口的关系
我们上文中提到对象没有iterator接口,用for...of遍历时便会报错。
let obj = { username: 'kobe', age: 39 }
for (let i of obj) {
console.log(i) // Uncaught TypeError: obj is not iterable
}
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
let obj = { username: 'kobe', age: 39 }
obj[Symbol.iterator] = function* myTest() {
yield 1;
yield 2;
yield 3;
};
for (let i of obj) {
console.log(i) // 1 2 3
}
上面代码中,Generator函数赋值给Symbol.iterator属性,从而使得obj对象具有了 Iterator 接口,可以被for of遍历了。
5.Generator的异步的应用
业务需求:
- 发送ajax请求获取新闻内容
- 新闻内容获取成功后再次发送请求,获取对应的新闻评论内容
- 新闻内容获取失败则不需要再次发送请求。
如何实现(前端核心代码如下):
function* sendXml() {
// url为next传参进来的数据
let url = yield getNews('http://localhost:3000/news?newsId=2');//获取新闻内容
yield getNews(url);//获取对应的新闻评论内容,只有先获取新闻的数据拼凑成url,才能向后台请求
}
function getNews(url) {
$.get(url, function (data) {
console.log(data);
let commentsUrl = data.commentsUrl;
let url = 'http://localhost:3000' + commentsUrl;
// 当获取新闻内容成功,发送请求获取对应的评论内容
// 调用next传参会作为上次暂停是yield的返回值
sx.next(url);
})
}
let sx = sendXml();// 发送请求获取新闻内容
sx.next();