generator

一、generator 函数

  • function关键字与函数名之间有一个星号

  • 函数体内部使用yield表达式,定义不同的内部状态

  • yield表达式只能用在 Generator 函数里面

  • 每个yield代表一个状态,每调用一次next()状态下移动一个

  • 最后一个是函数的return

  • 普通函数,在为变量赋值时就会执行,例如:let g = foo()。 Generator 函数只有调用next方法时,才会执行

1.1书写方式及调用

  • 函数执行时,并没有打印3,当执行第一次next()时,才会打印,在执行第二次next()时。打印4

  • 该函数的内容是否执行,执行到哪都由next()来控制,且外部的函数执行不会被阻塞(是不是想到了异步)

  • next方法返回一个对象,value属性就是当前yield表达式的值hello,done属性的值 false 还没结束,true 结束

1
2
3
4
5
6
7
8
9
10
11
// 推荐
function* foo(x, y) {
console.log(3)
let a = yield 3
console.log(4)
}

let g = foo()

g.next() // {value: 3, done: false}
g.next() // {value: 3, done: true}
  • 第一次执行g.next()时,Generator 函数foo的上下文会加入堆栈,即开始运行foo内部的代码。等遇到yield 1时,foo上下文退出堆栈,内部状态冻结

  • 第二次执行g.next()时,foo上下文重新加入堆栈,变成当前的上下文,重新恢复执行

  • yield是异步两个阶段的分界线。其后面代码要等到yield结束后执行即执行.next()后。yield等待多久都不会影响其他函数执行

1.2 yield

执行逻辑

  • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

  • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

  • 如果该函数没有return语句,则返回的对象的value属性值为undefined。

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

1
2
3
4
5
6
7
8
9
10
11
function* fss() {
let aa = 3
var reset = yield 1111;
console.log(reset) // 当传true时,该值发生了改变
if(reset) { aa = 4} // 该处重新赋值
console.log(aa)
}

var g = fss();
g.next()
g.next(true)

yield表达式如果用在另一个表达式之中,必须放在圆括号里面

1
2
3
4
5
6
7
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.3 for…of 循环

  • Generator 函数时生成的Iterator对象,且此时不再需要调用next方法

  • 一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象

  • Iterator 对象可以使用拓展运算…

1
2
3
4
5
6
7
8
9
10
11
12
13
function* aa () {
yield 1
yield 2
yield 3
yield 4
yield 5
return 6
}
for (let a of aa()) {
console.log(a)
}

console.log([...aa()])

1.4 yield* 表达式

yield与return区别, return语句不具备位置记忆的功能

如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。所以用yield*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* foo() {
yield 'a';
yield 'b';
}

function* bar() {
yield 'x';
yield* foo();
yield 'y';
}

for (let v of bar()){
console.log(v);
}

1.5 错误抛出

Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误

1
2
3
4
5
6
7
8
9
10
11
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('出错了');

1.6 return 返回值

return调用后, 返回值的done属性为true,以后再调用next方法,done属性总是返回true

1
2
3
4
5
6
7
8
9
10
11
function* gen() {
yield 1;
yield 2;
yield 3;
}

var g = gen();

g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }

1.7 状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}
};

1.8 异步封装

注意,back函数中的next方法,必须加上obj,因为yield表达式,本身是没有值的,总是等于undefined

  • 第一次执行next代码执行到yield后面的asyncData函数,1秒后执行back,在次调用next把异步返回的值作为上次next的参数传递进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function asyncData () {
console.log(3333)
setTimeout(() => {
back()
}, 1000)
}
function back () {
let obj = {a: 3}
m.next(obj)
}

function* foo () {
let result = yield asyncData()
console.log(result)
}

let m = foo()
m.next()

1.9 自执行,如果存在多个异步,需要等到前一个返回值在执行下一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let getAsyncData = function (obj) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(obj)
}, 1000)
})
}

function* foo () {
let result = yield getAsyncData({a: 3})
let result2 = yield getAsyncData({b: 4})
console.log(result)
console.log(result2)
}

function freeCarryOut (gen) {
let g = gen()

// 利用递归操作,如果发现result的done不是true,继续执行next,一个循环判断的过程。
function next (data) {
var result = g.next(data);
console.log(result)
if (result.done) {
return result.value;
} else {
result.value.then(function(data) {
next(data);
});
}
}

next();
}

freeCarryOut(foo)

2.0 监听设计

无限递增与监听,在开发时我们有时候会设计一种监听方式

generator 不会形成死循环,可以做无限递增操作,并在每次触发调用时,进行更新数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function callback (val) {
console.log(val)
}

function* f() {
for(var i = 0; true; i++) {
callback(i)
var reset = yield i;
}
}

var gfff = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
返回
顶部