Generator 生成器基础和 Async/Await 原理
ES6 提供的 API/方式
在 JS 中,生成器 Generator 是一种新的数据结构 它允许你定义一个函数,该函数可以根据需要一次返回(产出)一个值 而不是立刻返回所最终值,,可以在你需要的获取值
如何创建一个 Generator 生成器函数
- 把创建函数的“function”后面加一个“*”即可
- 箭头函数是无法变为生成器函数的
- 控制台详细输出 Generator 生成器函数,会有一个 isGenerator 这个属性,属性值为 true,并且不直接是 Function 的实例,是 GeneratorFunction 的实例
- 对于 ES6 中快捷方式创建的函数,直接在函数名前加*,就可以创建生成器函数了。
每一个生成器函数,都是 GeneratorFunction 这个类的实例
fn.__proto__
->GeneratorFunction.prototype->Function.prototype
多了一个私有属性[[IsGenerator]]:true
生成器函数执行
- 首先并不会立即把函数体中的代码执行
- 而是返回一个符合迭代器规范的迭代器对象itor 「按照原型链查找,先找到 GeneratorFunction.prototype,在它上面有
- next 执行的返回结果是一个对象 {done: ,value:}
- return 执行返回的结果也是一个对象{done:true,value:方法传入的值} 相当于在函数体中执行遇到了 return,结束整个函数的执行。不会报红,再次执行 next=>{done:true ,value:undefined}
- throw 方法 手动抛出异常「控制台报红」;生成器函数中的代码,都不会再执行了!!抛出异常后,下面的代码也不会执行了!!
继续向上查找,有 Symbol(Symbol.iterator)
继续向上查找 找到 Object」
- 当 itor.next()执行
- 把函数体中的代码开始执行
- 返回一个对象
- done:记录代码是否执行完毕
- value:记录本次处理的结果
Generator 生成器函数的作用:可以基于返回的 itor「迭代器对象」,基于其 next 方法,控制函数体中的代码,一步步执行!!!
在生成器函数体中有个关键词 yield
- 每一次执行 next,控制函数体中的代码开始执行「或者从上一次暂停的位置继续执行」,直到遇到 yield 则暂停!
done:false
value:yield 后面的值 - 当遇到函数体中的 return 或者已经执行到函数最末尾的位置了,
done:true
value:函数的返回值或者 undifined
=====================
传值处理
itor.next(N):每一次执行 next 方法,传递的值会作为上一个 yield 的返回值「所以第一次执行 next 方法,传递的值是没有用的,因为在它之前没有 yield」
/*
params:生成器函数接收的实参值,它是生成器函数执行时传递的值
fn(10,20,30)
params:[10,20,30]
*/
const fn = function* fn(...params) {
let x = yield 100;
console.log(x); //'second:222'
let y = yield 200;
console.log(y); //'three:333'
};
let itor = fn(10, 20, 30);
console.log(itor.next("first:111")); //=>{value:100,done:false}
console.log(itor.next("second:222")); //=>{value:200,done:false}
console.log(itor.next("three:333")); //=>{value:undefined,done:true}
===============
生成器函数的嵌套
const sum = function* sum() {
yield 300;
yield 400;
};
const fn = function* fu() {
yield 100;
yield* sum();// yield* 支持进入另外一个生成器函数中去一步步的执行
yield 200;
};
let itor = fn();
console.log(itor.next());//{value:100,done:false}
console.log(itor.next());//{value:300,done:false}
console.log(itor.next());//{value:400,done:false}
console.log(itor.next());//{value:200,done:false}
console.log(itor.next());//{value:undefined,done:true}
================
//需求:串行请求,有3个请求「请求需要的时间分分别是1000/2000/3000」
/* const delay=(interval=1000)=>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(`@@${interval}`)
},interval)
})
} */
/* delay(3000).then(value=>{
console.log(value)//=>@@3000
}) */
const delay = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`@@${interval}`)
}, interval)
})
}
//方式1 用promise来写
/* delay(1000)
.then(value=>{
console.log(`第一个请求成功-->`, value);
return delay(2000)
})
.then(value=>{
console.log(`第二个请求成功-->`, value);
return delay(3000)
})
.catch(reason=>{
console.log(`任何请求失败都走这里-->`, reason);
})
*/
//方式2 用async/await来写
/* (async ()=>{
try {
let value=await delay(1000);
console.log(`第一个请求成功-->`, value);
value=await delay(2000)
console.log(`第二个请求成功-->`, value);
value=await delay(3000)
console.log(`第三个请求成功-->`, value);
} catch (reason) {
console.log(`任何请求失败都走这里-->`, reason);
}
})() */
//3 基于Generator生成器函数,模拟Await语法,实现请求的串行效果
const handle = function* handle() {
let value = yield delay(1000);
console.log(`第一个请求成功-->`, value);
value = yield delay(2000)
console.log(`第二个请求成功-->`, value);
value = yield delay(3000)
console.log(`第三个请求成功-->`, value);
}
let itor = handle()
//done:是否执行完毕 value:获取的是每一次yield后面的值「Promise实例」
/* let { done, value } = itor.next()
value.then(x => {
//console.log(`x-->`, x);
//x:第一个请求成功的结果 @@1000
let { done, value } = itor.next(x) //第二次执行next,把第一个请求成功的结果 作为实参传入,目的是给上一个yield处理的返回值value赋值,所以此时输出的才能是@@1000
value.then(x => {
//console.log(`x-->`, x);
//x:第2个请求成功的结果 @@2000
let { done, value } = itor.next(x)
value.then(x => {
//console.log(`x-->`, x);
//x:第3个请求成功的结果 @@2000
let { done, value } = itor.next(x)
//此时done为true,不需要再往下处理了
})
})
}) */
//可以实现,但是比较恶心,根据代码的不同,next要执行不同的次数,并且每一次都是执行next方法{第一次不传值} done为false,执行next方法,如果遇到promise实例,调用.then获得一个结果,成功之后,执行下一次,直到done为true 可以用递归和循环的方式 「while循环/递归」
//编写通知Generator中代码逐一执行的方法
const AsyncFunction = function AsyncFunction(generator,...params) {
//params,传递给Generator函数中的值
let itor = generator(...params)
//基于递归的方法,通知Generator函数中的代码逐一执行
const next = x => {
//x 第一次不需要传值
let { done, value } = itor.next(x)
if (done) return
if (!(value instanceof Promise)) value = Promise.resolve(value)
/* value.then(x => {
next(x)
}) */
value.then(next)
}
next()
}
AsyncFunction(handle)
/* AsyncFunction(function* (x,y){
let total=x+y
let value=yield total
console.log("@1->",value);
yield delay(2000);
console.log("@2->",'哈哈哈');
},20,40) */
//等价于
/* (async (x,y)=>{
let total=x+y
let value=await total
console.log("@1->",value);
await delay(2000);
console.log("@2->",'哈哈哈');
})(20,40) */
ES8 (ECMAScript 2017)中,提供了async/await语法:用来简化Promise的操作,是Promise和Generator的语法糖,上面实现的AsyncFunction函数和Generator函数的配合使用就是async/await的原理!!
低版本的Node中,无法使用async/await。
co.js这个库,在Node中使用,可以让Node环境下,出现类似于async/await的效果