JavaScript中的Generator函数

前言

在学习Async的时候可以先学习generator函数

generator是ES6提出的一种特性,基于这个特性,可以去分段的执行函数

也就是函数执行到某一行,跳出之后可以重新进入到上次的状态

Generator

Generator 意思为生成器

在js中,Generator是一种特殊的函数

Generator的关键字为在function和括号之间加一个*

以及在函数体内使用yield关键字

1
2
3
function* fn() {
yield 1;
}

简单点讲,生成器函数可以在yield的位置暂停执行,返回yield紧接着的表达式的值

运行一个Generator函数会返回一个生成器对象

通过调用next方法可以得到迭代器(Iterator)对象

可以通过next放方法获取下一个yield的值

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

const iterator = fn();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: true }

通过执行函数得到一个生成器对象,这时候函数是还没有执行的

需要通过不断地调用next函数来进行执行

可以看出,next返回的对象value属性的值对应yield后面的表达式的值

done则是表明此时迭代是否结束的标志

这也就是迭代器的结构

如果我们不写return 3,而是直接返回的话,就可以使用循环来判断是否到达了迭代的末尾

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

const iterator = fn();
let item = iterator.next();
while(!item.done){
console.log(item.value); // 以此输出 1 2
item = iterator.next();
}

当然,我们可以给每次调用的next传入参数,传入的参数作为yield的返回值

1
2
3
4
5
6
7
8
9
10
function* fn() {
const r = yield 1;
yield r;
return;
}

const iter = fn();
console.log(iter.next('我是传入的参数')); // { value: 1, done: false }
console.log(iter.next()); // { value: '我是传入的参数', done: false }
console.log(iter.next()); // { value: undefined, done: true }

generator有什么好处呢

比如我们现在需要一个函数来生成唯一id的话

不使用generator可以这么写

1
2
3
4
5
6
7
8
9
let id = 0;
function getId() {
id++;
return id;
}

getId(); // 1
getId(); // 2
getId(); // 3

由于函数无法记忆状态,所以只能将变量放在全局

这样会造成变量污染

还可以使用闭包来改进上面的函数

1
2
3
4
5
6
7
8
9
10
11
const getId = (function getId() {
let id = 0;
return function (){
id++;
return id;
}
})();

getId(); // 1
getId(); // 2
getId(); // 3

如果使用generator,看起来会更加直观

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* getId() {
let id = 1;
let ct = yield id;
while(!ct) {
id++;
ct = yield id;
}
}

const gen = getId();

gen.next(); // 1
gen.next(); // 2
gen.next(); // 3
gen.next(true); // undefined 不想自增了,结束掉

在MDN上可以看到,除了next方法之外

generator对象还有returnthrow

return函数用于直接结束生成器,即把生成器置于完成状态

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

const gen = fn();

gen.next(); // { value: 1, done: false }
gen.return(); // { value: undefined, done: true }
gen.next(); // { value: undefined, done: true } 生成器结束了,next也就是返回最后的结果,这个最后的结果可能是执行到结束的结果,也可能是调用return的结果
gen.return(3); // { value: 3, done: true } 可以重复地调用return,根据参数返回对应的值

throw用于向生成器抛出一个异常

1
2
3
4
5
6
7
8
9
10
11
12
13
function* fn() {
try {
yield 1;
yield 2;
} catch (e) {
console.log(e); // 输出给你一个大嘴巴子
}
}

const gen = fn();

gen.next(); // 1;
gen.throw(new Error('给你一个大嘴巴子'));

throw返回的也是一个迭代器对象

如果此时生成器还能继续到达下一个yield的话

那么返回的就是下一个yield

如果不能到达,那么返回的迭代器对象里面的donetrue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* fn() {
try {
yield 1;
yield 2;
} catch (e) {
console.log(e); // 输出给你一个大嘴巴子
}
yield 3;
}

const gen = fn();

gen.next(); // 1;
gen.throw(new Error('给你一个大嘴巴子')); // 报错,返回了 { value: 3, done: false }

需要注意,ie不支持generator这个特性

后记

惆怅,许愿一个实习…