generator生成器的设计原理:
- 状态机,简化函数内部状态存储;
- 半协程实现
- 上下文冻结
应用场景:
- 异步操作的同步化表达
- 控制流管理
- 部署 Iterator 接口
- 作为数据结构
首先介绍几个概念:
coroutine美: [kəru'tin] n.联立程序;协同程序
function* asyncJob() { //协程的简单实现
// ...其他代码
var f = yield readFile(fileA);
// ...其他代码
}
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield
语句注明
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)
————————————————————————————————————————————————
generator 美: ['dʒenə.reɪtər] n.发电机;发生器;电力公司
首先介绍一下generator的意思,原意为发生器,在es6中是生成器的意思。
generator生成器和iterator遍历器是对应的,我们知道iterator遍历器是给不同数据结构提供统一的数据接口机制,那么相对的generator生成器是生成这样一个遍历器,进而使数据结构拥有iterator遍历器接口。换一种方法来说,generator函数提供了可供遍历的状态,所以generator是一个状态机,在其内部封装了多个状态,这些状态可以使用iterator遍历器遍历。
在形式上,generator函数有两个特征:
1,function关键字和函数名之间有一个*号:function* myGenerator(){}
2,,在函数内部使用yield关键字;yield英: [jiːld] v.提供,产生
function* myGenerator(){
yield 'hello';
yield 'world';
return 'ending'
}
//该函数有三个状态:hello,world 和 return 语句
注意:既然generator是一个状态机,所以直接运行myGenerator()函数,并不会执行,相反的是生成一个指向内部状态的指针对象,即一个可供遍历的遍历器。
想运行generator,必须调用遍历器对象的next
方法,使得指针移向下一个状态,直到遇到下一个yield
表达式(或return
语句)为止。
所以就可以得出generator函数的特征:
Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
const test = helloWorldGenerator();
test.next()
// { value: 'hello', done: false }
test.next()
// { value: 'world', done: false }
test.next()
// { value: 'ending', done: true }
test.next()
// { value: undefined, done: true }
另外:Generator 函数可以不用yield
表达式,这时就变成了一个单纯的暂缓执行函数。
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
与 Iterator 接口的关系
任意一个对象的Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象;
设置Symbol.iterator方法的函数,就等于设置对象的遍历器生成函数,所以可以将generator函数赋值给Symbol.iterator方法。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
由于generator生成器执行后生成一个遍历器对象,该遍历器对象也具有Symbol.iterator方法,而对应的值就是generator()
function* mygenerator(){
yield 1
}
const g = mygenerator()
g[Symbol.iterator]() === g
//g[Symbol.iterator]返回一个遍历器生成函数,执行后生成遍历器
//g是由generator()执行过生成的遍历器对象,二者相等
next方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。
next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
当next传入true参数后,这时上一个yield的值会被重置为true,这时reset = true,执行next方法时,
由于reset已经等于true,此时i= -1,执行i++ => 执行 yield 0 => 0;reset变为undefined
注意:
1,由于next
方法的参数表示上一个yield
表达式的返回值,所以在第一次使用next
方法时,传递参数是无效的。
2,V8 引擎直接忽略第一次使用next
方法时的参数,只有从第二次使用next
方法开始,参数才是有效的。
3,从语义上讲,第一个next
方法用来启动遍历器对象,所以不用带有参数。
next()方法入参的意义:
Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。
通过next
方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
如果想第一次就给next方法传参,可以给generator函数包一层,在外部函数中执行第一次next方法:
function* myGenerator(){
console.log(`${yield}`)
}
function wrapper(targetGenerator){
return function(...args){
let generatorObject = targetGenerator();
generatorObject.next();
return generatorObject;
}
}
//返回一个函数,该函数返回一个已经执行过第一次next()方法的遍历器对象。
const wrapperMyGenerator = wrapper(myGenerator);
const nowGenerator = wrapperMyGenerator();
nowGenerator.next('测试') //测试
generator的for...of...循环
该方法对于generator来说,注意一点,当generator返回值done === true时,循环结束,且循环不包括该返回对象。
从上面代码可见,使用for...of
语句时不需要使用next
方法。
给对象添加iterator的方法:
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
还可以赋值给对象的Symbol.iterator方法
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
return语句结束generator内部的状态,执行到return 再执行返回undefined
Generator的原型方法:
Generator.prototype.throw(),Generator.prototype.return()
throw() 在函数体外抛出错误,然后在 Generator 函数体内捕获
return():返回给定的值,并且终结遍历 Generator 函数
next()、throw()、return() 的共同点
作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield
表达式(带入参)
next()
是将yield
表达式替换成一个值throw()
是将yield
表达式替换成一个throw
语句return()
是将yield
表达式替换成一个return
语句
async函数:
实现原理:
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
异步遍历器:
asyncIterator
.next()
.then(
({ value, done }) => /* ... */
);
由于异步遍历器的next
方法,返回的是一个 Promise 对象。因此,可以把它放在await
命令后面。
async function f() {
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
console.log(await asyncIterator.next());
// { value: 'a', done: false }
console.log(await asyncIterator.next());
// { value: 'b', done: false }
console.log(await asyncIterator.next());
// { value: undefined, done: true }
}
for await...of
前面介绍过,for...of
循环用于遍历同步的 Iterator 接口。新引入的for await...of
循环,则是用于遍历异步的 Iterator 接口。