概述
状态机,封装多个状态,轮着输出,可以暂停,可以终止。可以对函数行为精确操作。
-
可以暂停的函数,
-
分段执行
-
分段输出
-
可以遍历
-
有记忆功能,下次遍历可以从上次继续下去
-
yield 只适合 genenrator函数,即带*的函数
-
yield作为输出,在函数内不能作为数据使用。比如
1+yield 2
-
yield
表达式本身没有返回值,或者说总是返回
undefined -
for…of循环不会执行return语句
表达式
var gfn = function * () {
yield 1 + 1; // 不会立即求值,只有next被调用才执行
yield 2;
return 3;
}
yield 定义不同的状态,是一种暂停标志,暂时输出
next 是一个函数继续执行的命令
g函数和普通函数对比
普通函数,立即执行,只执行一个return
generator函数,不会立即执行,执行多个yield
返回值
返回内部状态指针,可遍历对象
每次调用next()返回yield的状态,直到return或者函数结束
具体是
-
value: 内部状态值,即yield 后的表达式结果,
-
done: true/false 是否遍历结束
外部操作状态
-
next()带一个参数,修改上个表达式的值,调整函数行为
第一次使用next,没有上一个表达式,所以参数无效。
// 无限循环执行 function* g() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var gfn = g(); gfn.next() // { value: 0, done: false } gfn.next() // { value: 1, done: false } gfn.next(true) // { value: 0, done: false } // 第一次可以调用值的写法 // 包一层函数 function wrapper(generatorFunction) { return function (...args) { let generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; }; } const wrapped = wrapper(function* () { console.log(`First input: ${yield}`); return 'DONE'; }); wrapped().next('hello!') // First input: hello!
-
generator.throw() 扔出错误
只被内部接收
gfn.throw(new Error('我错了'))
-
generator.return() 终止循环
gfn.return(true) // 返回true,和函数return一样,不过是可以外面处理
g函数里面调用g函数,使用yield*
表达式,
yield* gfn 效果等同于for...of
gfn
有return的话需要赋值
let res = yield* gfn
一般使用
function* a () {
console.log('1')
console.log('2')
}
function *b() {
console.log('a')
yield* a()
console.log('b')
}
可遍历对象即可使用
function* g(){
yield* [1,2,3]
yield* 'hello'
}
let gfn = g()
gfn.next() // 1
...
gfn.next() // 'h'
...
对象里面的g函数
let obj = {
* gfn() {
yield 1
}
}
g函数改造成普通函数
g函数里面没有this,不能使用new
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() { // 2. 创建构造函数,使其可以new
// 1. 创建this, 使其可以使用this.a
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
状态机
// 一般的状态机,运行一次,改变一次执行状态
var ticking = true;
var clock = function() {
if (ticking)
console.log('Tick!');
else
console.log('Tock!');
ticking = !ticking;
}
//g 状态机,少了外部变量的定义,状态不会被非法篡改,更安全
var clock = function* () {
while (true) { // 一直next()无限循环
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
协程
多个线程(单线程情况下,即多个函数)可以并行执行,但是只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态(suspended),线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。
传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数
在内存中,子例程只使用一个栈(stack),而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行。
协程适合用于多任务运行的环境
有自己的执行上下文、可以分享全局变量
普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。
Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行
如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用
yield
表达式交换控制权Generator 函数它执行产生的上下文环境,一旦遇到
yield
命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next
命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
应用
- 暂缓执行的函数
let zh = function *() {
return 1 + 1
}
let zh1 = zh()
zh1.next()
面试可能会问你这个问题
- 实现斐波那契数列?
抄给他,问他在项目中哪里用到过,反正我是没用到过。见过面试不问项目,问网上试题全抄全问,两个人问了我两个半钟,后面二面后端技术总监问管理问题半个钟,最后过了没去那家,因为人事压工资。说实在,喜欢那个不看电脑随意问问题的面试官,技术闲聊都会香。
function* fibonacci() {
let [prev, curr] = [0, 1]; // 初始化数据
for (;;) { // 死循环
yield curr; // 输出1,1,2,3,5,8,13... 两个一直加
[prev, curr] = [curr, prev + curr]; // [1, 0+1=1], 换位递进
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
- 嵌套数组的平铺, 等同于 flat()
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
- 类数组结构
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
// task是一个函数,可以像回调函数那样使用它
}
- 部署iterator接口,让对象可以循环遍历,而不是使用
for...in
写法
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
- 流程控制
let steps = [step1Func, step2Func, step3Func];
function* iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yield step();
}
}
上面代码中,数组steps
封装了一个任务的多个步骤,Generator 函数iterateSteps
则是依次为这些步骤加上yield
命令。
将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。
let jobs = [job1, job2, job3];
function* iterateJobs(jobs){
for (var i=0; i< jobs.length; i++){
var job = jobs[i];
yield* iterateSteps(job.steps);
}
}
for(let step of iterateJobs(jobs)) {
console.log(step.id)
...
}
- 异步请求,如果不是很需要细粒度操作,一般还是使用
async-await
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();