1.Generator函数是什么?
是ES6提供的一种异步编程解决方案,使得书写异步代码更优雅。
2.有什么特点?怎么用?
特点:
(1)function关键字与函数名之间有一个*号。
(2)函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。执行Generator函数生成一个遍历器对象,用该对象调用其next()方法来遍历函数内部的状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
const hw = helloWorldGenerator();
(3)调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
3.注意:
(1)第一次调用Generator函数,返回遍历器对象。这时候,函数内部是没有执行的,仅仅返回遍历器对象。当调用next()方法时,函数才从头部开始执行,直到遇到yield表达式,则交出函数执行权,函数停止执行。
function *fn() {
console.log(1);
yield 1;
}
const g = fn(); // 这时候,函数内部的log语句不执行。控制台不会打印1的。
g.next(); // 这时候,控制台输出1
(2)遍历器对象调用next()方法,会移动内部指针,指向内部不同状态。假设函数未执行完,当指针还未指向下一状态时,则该指针一直指向上一个状态的。
(3) 每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next(); // 这里generator函数已经执行完了,并且,这里的赋值操作是没有完成的,在执行next()方法时,报错了,所以没有执行完next,就执行了catch
console.log('第二次运行next方法', v);
} catch (err) {
console.log('捕捉错误--', v); // 所以,这里v的值还是第一次执行的next时,所得到的值。{value: 1, done: false}
}
try {
v = generator.next();
console.log('第三次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
console.log('caller done');
}
log(g());
4.next()方法参数:
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
function *fn() {
const y = yield 1;
const result = y * 2;
console.log(result);
}
const g = fn()
g.next();
g.next(4); // 控制台打印8
(5)由生成器函数执行返回的遍历器对象,是生成器函数的实例。该遍历器对象的原型是该生成器函数的prototype对象。
function* gg() {
console.log(this);
const keyArr = Object.keys(this);
for (let key of keyArr) {
yield [key, this[key]]
}
}
console.log(gg().__proto__ === gg.prototype); // true
5.生成器函数的应用:
(1)为不能用for…of遍历的数据结构部署遍历器接口。ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器,还就是注意下,这里的this,指向的是对象。
function* gg() {
console.log(this);
const keyArr = Object.keys(this);
for (let key of keyArr) {
yield [key, this[key]]
}
}
const obj = {
name: 'ha',
sex: 'man',
age: 18,
id: 320320,
[Symbol.iterator]: gg
}
for (let key of obj) {
console.log(key);
}
(2)异步编程应用
Ajax 是典型的异步操作,通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response); // 这里将request("http://some.url")请求结果,赋值给了result,并且,最终函数执行完了。
});
}
var it = main();
it.next();
上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法(异步还是异步,这个是没有变得。只是写法上像同步而已)完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。
自动执行器
/**
* yield异步操作,被包装成promise对象
* @param count
* @returns {Promise<any>}
*/
let n = 0;
function readCount(count) {
return new Promise((resolve, reject) => {
setTimeout(() => {
n += count;
resolve(n);
}, 1000)
})
}
/**
* generator函数
* @returns {IterableIterator<*>}
*/
function * gen() {
let a = yield readCount(1);
let b = yield readCount(1);
return yield readCount(1);
}
/**
* 自动执行器
*/
function myCo(gen) {
const g = gen(); // 遍历器对象
let result = null;
function next(data) {
const { value, done } = g.next(data); // 遍历器对象调用next方法开始执行
if (!done){
value.then((res) => {
next(res);
})
} else {
result = value;
console.log(result);
}
}
next();
}
myCo(gen);