- 什么是Generator函数
Generator 函数式ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
- 状态机:封装了多个内部状态。
- 遍历器对象生成函数:返回值是一个遍历器对象。
- 语法
function * myGenerator() {
yield 'hello';
yield 1+1;
return 'wor' + 'ld';
}
const hw = myGenerator();
console.log(hw); // Object [Generator] {}
console.log(hw.next()); // { value: 'hello', done: false }
console.log(hw.next()); // { value: 2, done: false }
console.log(hw.next()); // { value: 'world', done: true }
console.log(hw.next()); // { value: undefined, done: true }
特征:
1. 星号: function 关键字和函数名之间有一个星号
function + * + 函数名
2. yield: 函数体内部使用 yield 表达式
yield 定义(产出)不同的内部状态,
3. 返回指针对象: 被调用时不会执行,返回值不是函数运行结果,返回值是一个指向内部状态的指针对象(遍历器对象 Iterator Object)
4. 使用next惰性求值: 必须调用遍历器对象的 next 方法,才能使指针移向下一个状态,遇到yield(或return)则暂停执行,重新调用 next 方法可以恢复执行。
5. value 和 done: 调用遍历对象的 next 方法时,next 方法返回一个包含value和done属性的对象,value属性的值是当前yield表达式的值,done属性的值要视乎遍历是否结束,未结束为false,结束为true(遍历结束时value属性的值为undefined)。
6. for...of自动遍历: for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
7. 除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
- yield 和 return
相似之处: 都能返回紧跟在后面的表达式的值。
不同之处:
- 遇到 yield 函数会暂停执行,下一次调用next时再从该位置继续向后执行;而 return 不具备位置记忆功能,一个函数内只能执行一次,之后再调用next,不管return语句后面是否还有其他语句,都返回value属性值为undefined,done属性值为true 的对象。
function * myGenerator() {
yield 'hello';
yield 'world';
return '此处是return语句';
yield 'say';
return 'bye';
}
const hw = myGenerator();
console.log(hw);
hw.next() // {value: 'hello', done: false}
hw.next() // {value: 'world', done: false}
hw.next() // {value: '此处是return语句', done: true}
hw.next() // {value: 'undefined', done: true}
hw.next() // {value: 'undefined', done: true}
- yield 表达式只能在 Generator 函数里使用。Generator 函数可以不用yield表达式,此时就变成一个单纯的暂缓执行函数,调用next() 执行
Generator 函数执行后,返回一个遍历器对象
返回的遍历器对象也具有 Symbol.iterator 属性,执行后返回自身。
function * myGenerator () {
......
}
var hw = myGenerator();
hw[Symbol.iterator]() === hw // true
next 方法的参数
next 方法可以带一个参数,该参数会被当做上一个 yield 表达式的返回值。
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next(); // Started
genObj.next(); // 1. undefined
genObj.next('a') // 2. a
genObj.next('b') // ---------此处执行return,无输出
console.log(genObj.next('b')); // { value: undefined, done: true }
- Generator 函数应用
1. 异步操作的同步化表达,改写回调函数
Generator 函数最大的特点就是可以交出函数的执行权,即暂停执行。
Generator 函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了
function showLoading() {
console.log('show-loading');
}
function getData() {
setTimeout(() => {
console.log('get-data');
hl.next(); // hide-loading
}, 1000)
}
function hideLoading() {
console.log('hide-loading');
}
function * handleLoading(){
showLoading();
yield getData();
hideLoading();
}
var hl = handleLoading();
hl.next(); // show-loading get-data
2. 控制流管理
传统写法,回调地狱
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
采用Promise改写
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
使用Generator函数进一步改善优化
注意,这种做法,只适合同步操作,即所有的task都必须是同步的,不能有异步操作。如要控制异步操作流程继续往下看。
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
3. 部署 Iterator 接口
利用 Generator 函数,可以在任意对象上部署 Iterator 接口。
详情参考
4. 作为数据结构
详情参考
如果对你有帮助了麻烦动动小指头点个赞,欢迎在下方评论区给我留言~~