赞
踩
Generator
函数 与 yield
关键字Generator
函数 与 yield
引入语法上:首先可以把它理解成,Generator
函数是一个状态机,封装了多个内部状态。
执行 Generator
函数会返回一个遍历器对象,也就是说,Generator
函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator
函数内部的每一个状态。
形式上: Generator函数是一个普通函数,但是有两个特征:
function
关键字与函数名之间有一个星号yield
表达式,定义不同的内部状态( yield 在英语里的意思就是“产出”)。示例:
function* count() { yield '1'; // yield表达式就是暂停标志,遇到 yield 表达式,就暂停执行后面的操作,并将紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。 yield '2'; yield '3'; return '4'; } var c = count(); // 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是:调用 `Generator` 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)**。我们需要一步步调用执行状态。 // 简单理解: `Generator` 函数是分段执行的,内部 `yield` 表达式是**暂停执行的标记**,而 `next` 方法可以恢复执行。所以`Generator`函数运行如下: // console.log(c) // [[GeneratorState]] : "suspended" //暂停、中止状态 c.next() // 由于yieid表达式暂停到此,调用 next 方法时,恢复执行,往下执行遇到下一个 yield 表达式,继续暂停。 // { value: '1', done: false } c.next() // { value: '2', done: false } c.next() // { value: '3', done: false } c.next() // { value: '4', done: true } // 此时的Generator函数状态 [[GeneratorState]] : "closed" // 关闭,表示状态已经执行完毕 c.next() // { value: undefined, done: true } // 状态已经完成,返回对象的value为undefined, 后续调用next()都是这个值
Generator
函数可以不用yield
表达式,这时就变成了一个单纯的暂缓执行函数。function* f() { console.log('执行了!') } var generator = f(); setTimeout(function () { generator.next() }, 2000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
yield
表达式只能用在Generator
函数里面yield
表达式如果用在另一个表达式之中,必须放在圆括号里面。function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
- 1
- 2
- 3
- 4
- 5
- 6
next()
方法传参yield
表达式本身没有返回值,或者说总是返回 undefined
。 next
方法可以带一个参数,该参数就会被当作上一个 yield
表达式的返回值。
示例:
function* foo(x) {
const y = yield x;
const z = '这是从第3个next获取的' + (yield y); // y需要自定义,从哪里来? next()传入,传入的参数是上一个yield表达式返回的值
yield z; // z同理
return 5
}
var a = foo(5);
console.log(a.next())
console.log(a.next(1)) // 如果不传参数,则输出undefined
console.log(a.next(2))
再看一个示例:
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false} 不带参数,导致 y 的值等于 2 * undefined (即 NaN ),除以 3 以后还是 NaN
a.next() // Object{value:NaN, done:true} 不带参数,所以 z 等于 undefined ,返回对象的 value 属性等于 5 + NaN + undefined ,即 NaN 。
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
for...of
遍历suspended
状态的Generator
函数for...of
循环可以自动遍历 Generator
函数运行时生成的Iterator
对象,且此时不再需要调用 next
方法。
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
上面代码使用 for...of
循环,依次显示 5 个 yield
表达式的值。这里需要注意,一旦 next
方法的返回对象的 done
属性为 true
, for...of
循环就会中止,且不包含该返回对象,所以上面代码的 return
语句返回的 6 ,不包括在 for...of
循环之中。
做个示例:
function* fibonacci() {
let [prev, curr] = [0, 1];
for (; ;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n); // 1 1 2 3 5 8 .... 987
}
for (; ;) {}
表示无限循环或无条件循环,必须设置break
终止循环var count = 1 for (; ;) { console.log(count) count++ if (count > 5) { break } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
thorw()
抛出错误Generator
函数返回的遍历器对象,都有一个 throw
方法,可以在函数体外抛出错误,然后在 Generator
函数体内捕获。
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
连续抛出两个错误。第一个错误被 Generator
函数体内的 catch
语句捕获。 i
第二次抛出错误,由于 Generator
函数内部的 catch
语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator
函数体,被函数体外的 catch
语句捕获。
示例2:
var g = function* () { try { yield 1; } catch (e) { console.log('内部捕获', e); } try { yield 2; } catch (e) { console.log('内部捕获1', e); } }; var i = g(); i.next() i.next() // 执行到此,已经进入第2个suspended的try..catch try { i.throw('a'); i.throw('a1'); i.throw('b'); } catch (e) { console.log('外部捕获', e); } // 内部捕获1 b // 外部捕获 a1
throw
方法可以接受一个参数,该参数会被 catch
语句接收,建议抛出 Error
对象的实例。
var g = function* () {
try {
yield;
} catch (e) {
console.log(e);
}
};
var i = g();
i.next();
i.throw(new Error('出错了!'));
// Error: 出错了!(…)
return()
Generator
函数返回的遍历器对象,还有一个 return
方法,可以返回给定的值,并且终结遍历 Generator
函数。
function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return(4) // { value: 4, done: true } 传入参数为终止时的返回值,如果不传则输出undefined g.next() // { value: undefined, done: true } // 等同于 function* gen() { yield 1; return 4 yield 2; yield 3; }
如果 Generator
函数内部有 try...finally
代码块,且正在执行 try
代码块,那么 return
方法会导致立刻进入 finally
代码块,执行完以后,整个函数才会结束。
function* numbers() { yield 1; try { yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); g.next() // { value: 1, done: false } g.next() // { value: 2, done: false } g.return(7) // { value: 4, done: false } g.next() // { value: 5, done: false } g.next() // { value: 7, done: true }
next()、throw()、return()
的共同点next() 、throw() 、 return()
这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator
函数恢复执行,并且使用不同的语句替换 yield
表达式。
next()
是将 yield
表达式替换成一个值。const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
throw()
是将 yield
表达式替换成一个 throw
语句。gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
return()
是将 yield
表达式替换成一个 return
语句。gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
yield*
表达式Generator
函数内部,调用另一个 Generator
函数。需要在前者的函数体内部,自己手动完成遍历。function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; // 手动遍历 foo() for (let i of foo()) { console.log(i); } yield 'y'; } for (let v of bar()){ console.log(v); } // x // a // b // y
foo
和 bar
都是 Generator
函数,在 bar
里面调用 foo
,就需要手动遍历 foo
。如果有多个 Generator
函数嵌套,写起来就非常麻烦。所以我们可以用yield*
代替for...of
function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同于 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同于 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()) { console.log(v); }
function* main(url, cb) { // showLoading(); try { yield request(url, cb); } catch (e) { console.log(e) } // hideLoading(); } function request(url, cb) { console.log(url) setTimeout(() => { // it.next() cb && cb('data') it.throw(new Error('报错了...')) }, 1000) } var it = main("http://some.url", (data) => { console.log(data) }); it.next();
Generator
与状态机Generator
是实现状态机的最佳结构。比如,下面的clock
函数就是一个状态机。var ticking = true;
var clock = function() {
if (ticking)
console.log('Tick!');
else
console.log('Tock!');
ticking = !ticking;
}
上面代码的 clock
函数一共有两种状态( Tick
和 Tock
),每运行一次,就改变一次状态。这个函数如果用 Generator
实现,就是下面这样。
var clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
上面的 Generator
实现与 ES5
实现对比,可以看到少了用来保存状态的外部变量 ticking
,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator
之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态
详情请点击了解
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。