看了许多材料后,感觉下面这三篇文章对于理解generator函数有非常大的帮助,囊括了常见用法、控制流程、循环操作等,理解的程度已经基本能满足现在的需求。

  1. https://davidwalsh.name/es6-generators
  2. https://davidwalsh.name/es6-generators-dive
  3. https://davidwalsh.name/async-generators

下面的内容为阅读材料时随手做的笔记,未经过整理。

关于generator函数的基本概念

这篇文章描述generator使用了...so powerful for the future of JS.

万万没想到!之前对generator的认知仅停留在是个新的函数种类,没想到这里厉害。

到目前已经遇到两种衍生的技术了:

  1. async函数

  2. saga结合成的redux-saga。

原文:

If you ‘ve ever read anything about concurrency or threaded programming, you may have seen the term ‘cooperative’, which basically indicates that a process(in our case, a function) itself chooses when it will allow an interruption, so that it can cooperate with other code. This concept is contrasted with ‘preemptive’, which suggests that a process/function could be interrupted against its will.

就是说,并行编程有两种,在一段程序运行的时候可以被打断,执行另一段程序,主动打断的称为 cooperative,被动打断的称为 preemptive。Generator函数就是前者,它通过yield关键字,从generator函数内部打断执行过程,需要从外部恢复执行。

Generator函数并不仅仅是简单的打断、启动这种流程控制,它还允许向generator函数的内部和外部进行双向的数据传递。

创建iterator

调用一个generator函数便会创建一个iterator,并且创建的过程并不会执行generator函数中的内容。

关于generator的工作流程

以下内容出自此处

  1. Advancing the Generator

使用next()函数

  1. Pass a Value To the Iterator

在function*函数中使用yield关键字

  1. Receive a Value From the Iterator

yield keyword can receive a value back from the iterator

1
2
3
4
5
6
7
8
9
10
const generatorFunction = function* () {
console.log(yield);
};

const iterator = generatorFunction();

iterator.next('foo');
iterator.next('bar');

// bar

好奇怪,为啥执行第一个next不输出foo,而是将这个值丢弃?

  1. 内部执行顺序

执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var foo, f;
foo = function* () {
console.debug('generator 1');
console.debug('yield 1', yield 'A');
console.debug('generator 2');
console.debug('yield 2', yield 'B');
console.debug('generator 3');
}

f = foo();

console.log('tick 1');
console.log(f.next('a'));
console.log('tick 2');
console.log(f.next('b'));
console.log('tick 3');
console.log(f.next('c'));
console.log('tick 4');
console.log(f.next('d'));

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
tick 1
generator 1
{ value: 'A', done: false }
tick 2
yield 1 b
generator 2
{ value: 'B', done: false }
tick 3
yield 2 c
generator 3
{ value: undefined, done: true }
tick 4
{ value: undefined, done: true }
  1. 使用for...of...循环

准则:

  1. The iteration will continue as long as done property is false.
  2. The for..of loop cannot be used in cases where you need to pass in values to the generator steps.
  3. The for..of loop will throw away the return value.
  1. 委托yield
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let index;

const foo = function* () {
yield 'foo';
yield * bar();
};

const bar = function* () {
yield 'bar';
yield * baz();
};

const baz = function* () {
yield 'baz';
}

for (index of foo()) {
console.log(index);
}

// foo
// bar
// baz

委托一个generator到另一个generator相当于将目标generator的函数体导入到目的generator中,
相当于下面这种代码形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let index;

const foo = function* () {
yield 'foo';
yield 'bar';
yield 'baz';
};

for (index of foo()) {
console.log(index);
}

// foo
// bar
// baz
  1. 用yield控制异步流程
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
const foo = (name, callback) => {
setTimeout(() => {
callback(name);
}, 100);
};

const curry = (method, ...args) => {
return (callback) => {
args.push(callback);
return method.apply({}, args);
};
};

const controller = (generator) => {
const iterator = generator();
const advancer = (response) => {
let state;
state = iterator.next(response);
if (!state.done) {
state.value(advancer);
}
};
advancer();
};

controller(function* () {
const a = yield curry(foo, 'a');
const b = yield curry(foo, 'b');
const c = yield curry(foo, 'c');

console.log(a, b, c);
});
  1. 带错误处理的yield异步流程控制
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
36
37
38
39
40
41
const foo = (parameters, callback) => {
setTimeout(() => {
callback(parameters);
}, 100);
};

const curry = (method, ...args) => {
return (callback) => {
args.push(callback);
return method.apply({}, args);
};
};

const controller = (generator) => {
const iterator = generator();
const advancer = (response) => {
if (response && response.error) {
return iterator.throw(response.error);
}

const state = iterator.next(response);

if (!state.done) {
state.value(advancer);
}
}
advancer();
};

controller(function* () {
let a, b, c;
try {
a = yield curry(foo, 'a');
b = yield curry(foo, {error: 'Something went wrong.'});
c = yield curry(foo, 'c');
} catch (e) {
console.log(e);
}

console.log(a, b, c);
});

generator的一个理解角度

以下部分出自此处

翻译其中的understanding部分:

当一个函数被调用,其中的命令会一个接一个的按顺序执行,函数能够通过return将一些值传给它的调用者。

1
2
3
4
function regular() {
doA();
doB();
}

generators的出现能够像对待一个program一样对待一个函数。这个函数能够根据你定义的规则执行。所以我们可以将这个generator函数成为一个program

为了执行一个program,我们需要一个interpreter,这个interpreter将“take the program in and run it”。

1
2
3
interpreter(function* program() {

});

yield命令告诉一个program跳出generator调到interpreter中。programinterpreter的交流是双向的:

  1. program可以向interpreter发送一些东西
  2. interpreter也可以向program返回一些东西

基于上面的描述,可以从下面的角度描述下面这行代码:

const a = yield b;这行代码表示了我们如何发送一个命令b到interpreter然后把它的结果给a。generator函数将会暂停,直到interpreter告诉它才会继续执行后面的代码。

从redux-saga的角度阐述这个概念。

redux-saga没有将side-effect分散进多个action creator 和 reducer中,而是按逻辑组合多个片段行为,这个组合体被成为一个saga

redux-saga中的helper函数,相当于在翻译command

1
2
3
4
5
6
function* welcomeSaga() {
yield take('REGISTRATION_FINISHED');
yield put(showWelcomePopup());
}

sagaMiddleware.run(welcomeSaga);

sagaMiddleware感觉就是一个interpreter。