Generator 函数

1.Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,通常有两个特征:

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

  • 函数体内部使用yield表达式,定义不同的内部状态
    //一个简单的 Generator 函数
    function *Generator(){
    yield 'Hello';
    yield 'World';

     return 'Hello World';
    }

2.Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,必须调用 next 方法,才能使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。实际上就是,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

function *Generator(){
yield 'Hello';
yield 'World'; return 'Hello World';
} let generator = Generator(); //无返回 generator.next();
//{"value":"Hello","done":false} generator.next();
//{"value":"World","done":false} generator.next();
//{"value":"Hello World","done":true} generator.next();
//{"value":undefined , "done":true}

上述代码就是一个 Generator 函数的执行过程 :

  • 第一次调用 next() 方法, 遇到第一个 yield 表达式后返回一个对象,对象的 vlaue 属性值是第一个 yield 表达式的值 Hello , done属性是 false , 表示整个遍历还没有结束;
  • 第二次调用 next() 方法, Generator 函数从上次yield表达式停下的地方继续执行 , 遇到第二个 yield 表达式后返回一个对象,对象的 vlaue 属性值是第一个 yield 表达式的值 World , done属性是 false , 表示整个遍历还没有结束;
  • 第三次调用 next() 方法, Generator 函数从上次yield表达式停下的地方继续执行 ,一直执行到return语句(如果没有return语句,就执行到函数结束)。此时 next 方法返回的对象的value属性,就是 return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束;
  • 第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

3.上述例子中我们可以得知,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式(或return)后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

4.yield 表达式可以看做是Generator 函数的暂停标志,要注意的是 yield 表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行;

function* gen() {
yield 123 + 456;
}

上面代码中,yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值(“惰性求值”);

5.yield 表达式与 return 语句都能返回紧跟在语句后面的那个表达式的值,不同的是遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而 return 语句不具备位置记忆的功能,而且一个函数里面,只能执行一个 return 语句,但是可以执行多个yield表达式,要注意的是 yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错。

(function (){
yield 1;
})()
// SyntaxError: Unexpected number

6.Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数

function *Generator() {
console.log('Hello World!')
} var generator = Generator(); setTimeout(function () {
generator.next()
}, 2000); // "Hello World"

7.yield 表达式如果用在另一个表达式之中,必须放在圆括号里面,如果用作函数参数或放在赋值表达式的右边,可以不加括号

function *Generator() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
} function *Generator() {
foo( yield 'a' , yield 'b' ); // OK
let input = yield; // OK
}

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

function *Generator() {
for(var i = 0; true; i++) {
var reset = yield i;
if( reset ) { i = -1; }
}
} var g = Generator(); g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

上述代码中,定义了一个可以无限运行的 Generator 函数,如果 next 方法没有参数,每次运行到 yield 表达式,变量 reset 的值总是 undefined ,当 next 方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。

我们利用这个特性,在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为;

function *foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
} var a = foo(5);
a.next() // {value:6, done:false}
a.next() // {value:NaN, done:false}
a.next() // {value:NaN, done:true} var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

上述代码 a 第二次运行 next 方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以3以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN;

b 调用加了参数,结果就不一样了,第一次调用next方法时,返回 x+1 的值 6;第二次调用next方法,将上一次 yield 表达式的值设为 12 ,因此 y 等于 24,返回 y / 3 的值 8;第三次调用next方法,将上一次 yield 表达式的值设为 13 ,因此 z 等于 13 ,这时 x 等于 5,y 等于24,所以 return 语句的值等于 42;

ps:next 方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的

9.for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法;

function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
} for ( let v of foo() ) {
console.log(v);
} // 1 2 3 4 5

上述代码,使用了 for ... of 循环 , 依次打印了5个 yield 表达式的值,此时就不需要 next 方法了,但是要注意的是,一旦 next 方法的返回对象的 done 属性为 true时,for...of循环就会中止,且不包含该返回对象,因此上面 return 语句返回的 6,不包括在for...of循环之中。

10.Generator函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历Generator函数

function *Generator() {
yield 1;
yield 2;
yield 3;
} var g = Generator(); g.next() // { value: 1, done: false }
g.return('ok') // { value: "ok", done: true }
g.next() // { value: undefined, done: true }

遍历器对象 g 调用 return 方法后,返回值的 value 属性就是 return 方法的参数 ok(如果 不提供参数,则返回值的 value 属性为 undefined) 。并且,Generator函数的遍历就终止了,返回值的 done 属性为 true,以后再调用 next 方法,value 总是返回 undefined , done 属性总是返回 true;

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

function *Generator1() {
yield 'a';
yield 'b';
} function *Generator2() {
yield 'x';
Generator1();
yield 'y';
} for (let v of Generator2()){
console.log(v);
}
// "x"
// "y"

12.可以使用 yield* 表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数来达到在 Generator 函数内部,调用另一个 Generator 函数的效果;

function *Generator1() {
yield 'a';
yield 'b';
} function *Generator2() {
yield 'x';
yield* Generator1();
yield 'y';
} // 等同于
function *Generator2() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
} // 等同于
function *Generator2() {
yield 'x';
for (let v of Generator2()) {
yield v;
}
yield 'y';
} for (let v of Generator2()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"

13.Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景;

  • 异步操作的同步化表达 , 可以利用 Generator 函数的暂停执行的效果,把异步操作写在 yield 表达式里面,等到调用next方法时再往后执行
    function *loading() {
    showLoadingScreen();
    yield loadDataAsync();
    hideLoadingScreen();
    }
    var loader = loading();
    // 加载loading
    loader.next()

     // 隐藏loading
    loader.next()

上面代码中,第一次调用 loading 函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面(showLoadingScreen),并且异步加载数据(loadDataAsync)。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面,整个逻辑就很清晰了~

  • 可以利用 Generator 函数用同步的方式部署 Ajax 操作

     function *main(url) {
    var result = yield request( url );
    var resp = JSON.parse(result);
    console.log(resp.value);
    } function request(url) {
    makeAjaxCall(url, function(response){
    it.next(response);
    });
    } var ajax = main();
    ajax.next();

上面代码的 main 函数,就是通过 Ajax 操作获取数据,要注意的是 makeAjaxCall 函数中的 next 方法,必须加上 response 参数,因为 yield 表达式,本身是没有值的,总是等于undefined;

  • 控制流管理,如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样:

     step1(function (value1) {
    step2(value1, function(value2) {
    step3(value2, function(value3) {
    step4(value3, function(value4) {
    // Do something with value4
    });
    });
    });
    });

如果采用 Promise 写法 ,

     Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})

采用 Generator 函数来写 :

    function *longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}

然后使用一个自动化函数,按次序执行所有步骤

    scheduler(longRunningTask(initialValue));

    function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}

14.另外,Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行),遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行,另外,它的函数体内外的数据交换和错误处理机制的特点使它可以作为异步编程的完整解决方案;

ES6必知必会 (七)—— Generator 函数的更多相关文章

  1. 2015 前端[JS]工程师必知必会

    2015 前端[JS]工程师必知必会 本文摘自:http://zhuanlan.zhihu.com/FrontendMagazine/20002850 ,因为好东东西暂时没看懂,所以暂时保留下来,供以 ...

  2. [ 学习路线 ] 2015 前端(JS)工程师必知必会 (2)

    http://segmentfault.com/a/1190000002678515?utm_source=Weibo&utm_medium=shareLink&utm_campaig ...

  3. mysql必知必会

    春节放假没事,找了本电子书mysql必知必会敲了下.用的工具是有道笔记的markdown文档类型. 下面是根据大纲已经敲完的章节,可复制到有道笔记的查看,更美观. # 第一章 了解SQL## 什么是S ...

  4. msql 必知必会笔记

    Edit Mysql 必知必会 第一章 理解SQL 什么是数据库 数据库(database) 保存有组织的数据的容器 什么是表  一组特定类型的数据的结构化清单 什么是模式  数据库和表的布局及特性的 ...

  5. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

  6. 《MySQL 必知必会》读书总结

    这是 <MySQL 必知必会> 的读书总结.也是自己整理的常用操作的参考手册. 使用 MySQL 连接到 MySQL shell>mysql -u root -p Enter pas ...

  7. 《SQL必知必会》学习笔记(一)

    这两天看了<SQL必知必会>第四版这本书,并照着书上做了不少实验,也对以前的概念有得新的认识,也发现以前自己有得地方理解错了.我采用的数据库是SQL Server2012.数据库中有一张比 ...

  8. SQL 必知必会

    本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...

  9. 《MySQL必知必会》[01] 基本查询

    <MySQL必知必会>(点击查看详情) 1.写在前面的话 这本书是一本MySQL的经典入门书籍,小小的一本,也受到众多网友推荐.之前自己学习的时候是啃的清华大学出版社的计算机系列教材< ...

  10. python网络爬虫,知识储备,简单爬虫的必知必会,【核心】

    知识储备,简单爬虫的必知必会,[核心] 一.实验说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou 2. 环境介绍 本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌 ...

随机推荐

  1. C Strange Sorting

    C. Strange Sorting time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  2. EF Code First 学习笔记:约定配置(转)

      要更改EF中的默认配置有两个方法,一个是用Data Annotations(在命名空间System.ComponentModel.DataAnnotations;),直接作用于类的属性上面;还有一 ...

  3. jQuery源码分析--Event模块(1)

    jQuery的Event模块提供了强大的功能:事件代理,自定义事件,自定义数据等.今天记录一下它实现的原理. 我们都知道,在js的原生事件中,有事件对象和回调函数这两样东西.但是事件对象是只读的,所以 ...

  4. 抓包工具Charles简单使用介绍

    一是拦截别人软件的发送的请求和后端接口,练习开发. 二是自己后端返回的response拦截修改后再接收以达到测试临界数据的作用. 三写脚本重复拦截抓取别人的数据. 四支持流量控制,可以模拟慢速网络以及 ...

  5. php-fpm开启报错-ERROR: An another FPM instance seems to already listen on /tmp/php-cgi.sock

    在升级了php7.2.0版本之后,重新启动php-fpm过程中遇到一个报错. An another FPM instance seems to already listen on /tmp/php-c ...

  6. 【同步时间】Linux设置时间同步

    所有节点都要确保已安装ntpd(在步骤4已安装) 1.首先选择一台服务器作为时间服务器. 假设选定为node1.sunny.cn服务器为时间服务器. 2.ntp服务器的配置 修改ntp.conf文件: ...

  7. LA 3268 号码簿分组(最大流+二分)

    https://vjudge.net/problem/UVALive-3268 题意: 有n个人和m个组.一个人可能属于很多组.现在请你从某些组中去掉几个人,使得每个人只属于一个组,并使得人数最多的组 ...

  8. HDU 3820 Golden Eggs

    http://acm.hdu.edu.cn/showproblem.php?pid=3820 题意:n*m的格子,每个格子放金蛋或银蛋,每个格子的金蛋和银蛋都有一个对应的点权,如果有两个金蛋相连,则需 ...

  9. 使用CAS实现无锁的SkipList

    无锁 并发环境下最常用的同步手段是互斥锁和读写锁,例如pthread_mutex和pthread_readwrite_lock,常用的范式为: void ConcurrencyOperation() ...

  10. Rails 5 Test Prescriptions 第4章 什么制造了伟大的测试

    伴随着程序成长,测试变长,复杂性增加,如何更高效的写测试,对以后开发不会造成麻烦. 测试本身没发被测试,所以一定要清楚,可控.不要加循环,不要过于复杂的自动编程. Cost and Value 成本和 ...