简介
基本概念
Generator 是 ES6 提供的一种异步编程解决方案。可以把它理解成一个状态机,并且会返回一个遍历器对象
- 有以下两个特征
- function关键字和函数名之间有一个星号
*
- 函数体内部使用
yield
表达式
- 执行大致过程
- 生成 Iterator 之后,第一次调用
next
方法,直到遇见第一个yield
表达式为止。此时返回一个对象,value
就是yield
表达式的值,同时done
为false - 按照此规律执行,当执行到return语句是,
next
方法还是返回一个对象,该对象的value
就是return
返回的值,同时done
为ture - 如果没有
return
,执行完所有的yield
,done
还是为false
。需要再来一下next
yield 表达式
- generator 函数中可以不使用
yield
,此时变成了一个单纯的暂缓执行函数 - 在普通函数,匿名函数中使用
yield
表达式,会产生一个错误 - 如果
yield
表达式在另一个表达式中,必须放在圆括号内
// 情景3示例
function* demo() {
console.log('Hello' + (yield 99)); // OK
console.log('Hello' + (yield 123)); // OK
}
next 方法參數
next 方法可以带一个参数,该参数会被当作上一个yield表达式的返回值
基础案例分析
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var b = foo(5);
b.next(); // {value:6,done:false}
b.next(12); // {value:8,done:false}
// 此时 x=5,y=24,z=13
b.next(13); // {value:42,done:true}
- 第一个使用
next
不需要传值
进阶案例分析
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
genObj.next('a');
genObj.next('b');
- 第一次调用next,执行到第二条语句暂停。因此打印'Started'
- 第二次调用next,传入
a
,作为上次yield
的返回值,打印1. a
。继续执行到yield
暂停 - 第三次调用next,传入
b
,作为上次yield
的返回值,答应2. b
。继续执行,此时{value:'result',done:true}
for...of
循环
斐波那契数列
function* feibonacci() {
let [prev,curr] = [0,1];
for (;;) {
[prev,curr] = [curr,prev+curr]
yield curr;
}
}
// num 是值的大小,不是个数
for (let num of feibonacci()) {
if (num > 100) break; // ---> break 会触发遍历器的return方法,return 方法其实一般就是返回`return {done:true}`
console.log(num);
}
对象扩展遍历器
在对象的
[Symbol.iterator]
上挂载遍历器
function* objectEntries() {
let objKeys = Object.keys(this);
for (let key of objKeys) {
yield [key,this[key]]
}
}
let jane = { first: 'Jane', last: 'Doe'};
// 只能绑定到单个的对象上
jane[Symbol.iterator] = objectEntries;
for (let [key,val] of jane) {
console.log(key,val)
}
throw方法
遍历器对象都有一个
throw
方法,在外部抛出错误之后,内部能够捕获
基础知识
外部抛出错误
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获',e)
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获',e);
}
// 内部捕获 a
// 外部捕获 b
- 遍历器
i
执行next
方法 - 遍历器
i
在外部抛出错误,被内部的捕获。同时,throw
方法传递的参数当作catch
的参数 - 遍历器再次抛出错误,
catch
已经执行过了,就不执行了,因此被外部的try catch
捕获 - 可以内部捕获外部抛出的错误,也可以外部捕获抛出的错误
-
throw
方法,也就是抛出错误的方法,默认执行一次next
内部抛出错误
function* foo() {
var x = yield 3;
var y = x.toUpperCase();
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
- 由于
next(42)
后,x为数字42
没有toUpperCase
方法。因此,将抛出一个错误 - 抛出的错误将在外部被捕获
return
方法
- 使用
return
方法
- 当有参数时,返回给定的值
- 当没有参数,返回
undefined
-
Generator
函数内有try...finally
代码块,那么return
方法会推迟到finally
代码执行完再执行
function* gen() {
yield 1;
yield 2;
}
var g = gen();
g.next(); // {value: 1,done: false}
g.return('foo'); // {value: 'foo',done: true}
g.next(); // {value: undefined,done: false}
// finally
function* numbers () {
try {
yield 2;
} finally {
yield 4;
}
yield 6;
}
var g = numbers();
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false } --- 执行finally中语句
g.next() // { value: 7, done: true } --- 执行完finnally中语句之后,再执行return语句
next,throw,return 共同点
它们作用都是让 Generator 函数回复执行
yield*
表达式
yield*
表达式,代表它后面是一个遍历器对象,在没有return
语句的情况下,相当于使用for of
- 在
Generator
函数中不能直接调用Generator
函数,需要使用yield*
基本案例
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// Generator bar 相当于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
// 调用 bar
for (let v of bar()) {
console.log(v);
}
// 结果是:x,a,b,y
yield*
后边值的种类
任何数据结构只要有 Iterator 接口,就可以被yield*遍历。
yield* 'hello'
yield* [1,2,3]
有return的Generator函数
yield* foo()
,如果foo函数有return,那么该表达式是有返回值的
function *foo() {
yield 2;
yield 3;
return "foo";
}
function *bar() {
yield 1;
var v = yield *foo();
console.log( "v: " + v );
yield 4;
}
var it = bar();
it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// 相当于没有调用 next 方法,只是将返回值返回给表达式
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
取出嵌套数组
function* tree(arr) {
if (Array.isArray(arr)) {
for (let i = 0;i < arr.length;i++) {
yield* tree(arr[i]);
}
}else {
yield arr;
}
}
const arr = [1,[2,3],[4,5],6,7]
for (let i of tree(arr)) {
console.log(i);
}
- 注意两个地方
- 如果使用
yield tree
,也就是没有*
,那么调用Generator
方法是没有什么效果的 - 使用
let
声明的变量进行递归没有关系
作为对象属性的Generator函数
简写成下面的形式
let obj = {
* myGeneratorMethod() {
}
}
Generator 函数的this
Generator的返回值
- Generator 函数总是返回一个遍历器,这个遍历器是 Generator 的实例
- 使用
instanceof
检测,为true - Generator 原型上的方法,将被遍历器继承
function* p() {
}
p.property.say = function () {
console.log('hello')
}
let s = p();
s instanceof p; // true
s.say(); // 'hello'
Generator 中 this 基础知识
- 即使“返回值”是“Generator”函数实例,但是“Generator”中this上绑定的属性,“返回值”上并不会有
- 不能对 Generator 函数直接使用 new 命令
应用
异步操纵的同步化表达
// loading 应用
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
loader.next();
loader.next();
- 第一次执行
next
方法时,展示加载页面,同时发送异步请求 - 其实第二次执行
next
方法,应该是异步请求完毕的时候
// Ajax 应用
function request(url){
// 是个伪代码,其实就是一个发送 ajax 请求的方法
makeAjaxCall(url,function (response) {
it.next(reponse);
})
}
function* main() {
var result = yield request('http://some.url');
var resp = JSON.parse(result);
console.log(resp.value)
}
var it = main();
it.next();
- 第一次执行
next
方法,发送ajax请求 - 在成功响应的回调函数中,第二次执行
next
方法,该方法将response
当作上一次的返回值
控制流管理
这里只介绍最简单的一种方法
let steps = [step1Func, step2Func, step3Func];
function *iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yield step();
}
}
// 之后使用 for of 即可