浅析Generator

Generator 函数就是遍历器生成函数

1
2
3
4
5
6
7
8
function* foo () {
console.log(1);
yield 1;
console.log(2);
yield 2;
console.log(3);
yield 3;
};

调用foo()会生成迭代器对象。

1
const generator = foo();

第一次执行generator.next(),指针指向的是yield 1;的执行结果。

console.log(1)则是会被放在next中执行,这是猜想!

迭代器对象的原理可以参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

1
2
3
4
5
6
7
8
function* gen(){
// some code
}

var g = gen();

g[Symbol.iterator]() === g
// true

在调用next方法时会返回一个对象,但是在generator内部,yield表达式是不会有返回值(或者说是返回undefind)。

但是想起在koa的时候,下面的方法又是可以有返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
url: '/detail',
method: 'GET',
* handler() {
const id = this.headers[setting.bizHeader];
if (!id) { return; }
const out = yield model.Business.findOne({
raw: true,
attributes: ['name', 'desc', 'avatar'],
where: { bizId: id & 65535, CompanyId: id >> 16 }
});
return out;
}
};

// out 会储存一个Business的对象

next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

比如上面的handler generator函数,只要如是操作:

1
2
3
4
5
6
const generator = handler();
// 计算 const out = yield model.Business.findOne({ /* ... */ })
const res = generator.next(); // { value: /* Business对象 */, done: false }
// res.value就会赋值给out
generator.next(res.value);
// 最终就return out

从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数

generator的throw方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* foo() {
try {
yield 'isaac';
} catch (e) {
console.log(`inner error:`, e.message);
}
}

try {
const generator = foo();
// 必须先启动,调用throw方法才有效
generator.next();
generator.throw('i am a error');
} catch (e) {
console.log('outer error:', e.message);
}

// output
// inner error: i am a error

正如上面例子,使用generator.throw方法抛出的错误会被generator函数内部的try...catch捕获到,当然如果generator函数内部没有使用try...catch则会冒泡想外层抛出。

yield*的本质

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}

// 等同于

function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}

遍历迭代器的同时再生成迭代器

Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实

generator对异步任务的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var fetch = require('node-fetch');

function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});