Generator函数的应用

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

异步操作的同步化表达

Generator函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

  1. function* loadUI() {
  2. showLoadingScreen();
  3. yield loadUIDataAsynchronously();
  4. hideLoadingScreen();
  5. }
  6. var loader = loadUI();
  7. // 加载UI
  8. loader.next()
  9. // 卸载UI
  10. loader.next()

上面代码中,第一次调用loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面(showLoadingScreen),并且异步加载数据(loadUIDataAsynchronously)。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰。

Ajax 是典型的异步操作,通过 Generator函数部署 Ajax 操作,可以用同步的方式表达。

  1. function* main() {
  2. var result = yield request("http://some.url");
  3. var resp = JSON.parse(result);
  4. console.log(resp.value);
  5. }
  6. function request(url) {
  7. makeAjaxCall(url, function(response){
  8. it.next(response);
  9. });
  10. }
  11. var it = main();
  12. it.next();

上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined

下面是另一个例子,通过 Generator函数逐行读取文本文件。

  1. function* numbers() {
  2. let file = new FileReader("numbers.txt");
  3. try {
  4. while(!file.eof) {
  5. yield parseInt(file.readLine(), 10);
  6. }
  7. } finally {
  8. file.close();
  9. }
  10. }

上面代码打开文本文件,使用yield表达式可以手动逐行读取文件。

控制流管理

如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。

  1. step1(function (value1) {
  2. step2(value1, function(value2) {
  3. step3(value2, function(value3) {
  4. step4(value3, function(value4) {
  5. // Do something with value4
  6. });
  7. });
  8. });
  9. });

采用 Promise 改写上面的代码。

  1. Promise.resolve(step1)
  2. .then(step2)
  3. .then(step3)
  4. .then(step4)
  5. .then(function (value4) {
  6. // Do something with value4
  7. }, function (error) {
  8. // Handle any error from step1 through step4
  9. })
  10. .done();

上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator函数可以进一步改善代码运行流程。

  1. function* longRunningTask(value1) {
  2. try {
  3. var value2 = yield step1(value1);
  4. var value3 = yield step2(value2);
  5. var value4 = yield step3(value3);
  6. var value5 = yield step4(value4);
  7. // Do something with value4
  8. } catch (e) {
  9. // Handle any error from step1 through step4
  10. }
  11. }

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

  1. scheduler(longRunningTask(initialValue));
  2. function scheduler(task) {
  3. var taskObj = task.next(task.value);
  4. // 如果Generator函数未结束,就继续调用
  5. if (!taskObj.done) {
  6. task.value = taskObj.value
  7. scheduler(task);
  8. }
  9. }

注意,上面这种做法,只适合同步操作,即所有的task都必须是同步的,不能有异步操作。因为这里的代码一得到返回值,就继续往下执行,没有判断异步操作何时完成。如果要控制异步的操作流程,详见后面的《异步操作》一章。

下面,利用for...of循环会自动依次执行yield命令的特性,提供一种更一般的控制流管理的方法。

  1. let steps = [step1Func, step2Func, step3Func];
  2. function *iterateSteps(steps){
  3. for (var i=0; i< steps.length; i++){
  4. var step = steps[i];
  5. yield step();
  6. }
  7. }

上面代码中,数组steps封装了一个任务的多个步骤,Generator函数iterateSteps则是依次为这些步骤加上yield命令。

将任务分解成步骤之后,还可以将项目分解成多个依次执行的任务。

  1. let jobs = [job1, job2, job3];
  2. function* iterateJobs(jobs){
  3. for (var i=0; i< jobs.length; i++){
  4. var job = jobs[i];
  5. yield* iterateSteps(job.steps);
  6. }
  7. }

上面代码中,数组jobs封装了一个项目的多个任务,Generator函数iterateJobs则是依次为这些任务加上yield*命令。

最后,就可以用for...of循环一次性依次执行所有任务的所有步骤。

  1. for (var step of iterateJobs(jobs)){
  2. console.log(step.id);
  3. }

再次提醒,上面的做法只能用于所有步骤都是同步操作的情况,不能有异步操作的步骤。如果想要依次执行异步的步骤,必须使用后面的《异步操作》一章介绍的方法。

for...of的本质是一个while循环,所以上面的代码实质上执行的是下面的逻辑。

  1. var it = iterateJobs(jobs);
  2. var res = it.next();
  3. while (!res.done){
  4. var result = res.value;
  5. // ...
  6. res = it.next();
  7. }

部署 Iterator 接口

利用 Generator函数,可以在任意对象上部署 Iterator 接口。

  1. function* iterEntries(obj) {
  2. let keys = Object.keys(obj);
  3. for (let i=0; i < keys.length; i++) {
  4. let key = keys[i];
  5. yield [key, obj[key]];
  6. }
  7. }
  8. let myObj = { foo: 3, bar: 7 };
  9. for (let [key, value] of iterEntries(myObj)) {
  10. console.log(key, value);
  11. }
  12. // foo 3
  13. // bar 7

上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了 Iterator 接口。也就是说,可以在任意对象上部署next方法。

下面是一个对数组部署 Iterator 接口的例子,尽管数组原生具有这个接口。

  1. function* makeSimpleGenerator(array){
  2. var nextIndex = 0;
  3. while(nextIndex < array.length){
  4. yield array[nextIndex++];
  5. }
  6. }
  7. var gen = makeSimpleGenerator(['yo', 'ya']);
  8. gen.next().value // 'yo'
  9. gen.next().value // 'ya'
  10. gen.next().done // true

作为数据结构

Generator 可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。

  1. function *doStuff() {
  2. yield fs.readFile.bind(null, 'hello.txt');
  3. yield fs.readFile.bind(null, 'world.txt');
  4. yield fs.readFile.bind(null, 'and-such.txt');
  5. }

上面代码就是依次返回三个函数,但是由于使用了 Generator函数,导致可以像处理数组那样,处理这三个返回的函数。

  1. for (task of doStuff()) {
  2. // task是一个函数,可以像回调函数那样使用它
  3. }

实际上,如果用 ES5 表达,完全可以用数组模拟 Generator 的这种用法。

  1. function doStuff() {
  2. return [
  3. fs.readFile.bind(null, 'hello.txt'),
  4. fs.readFile.bind(null, 'world.txt'),
  5. fs.readFile.bind(null, 'and-such.txt')
  6. ];
  7. }

上面的函数,可以用一模一样的for...of循环处理!两相一比较,就不难看出 Generator 使得数据或者操作,具备了类似数组的接口。

es6 Generator函数的应用相关推荐

  1. [ES6] Generator 函数

    [ES6] Generator 函数 Generator 函数与普通函数的区别 执行机制 Generator 函数返回的遍历器对象的方法 循环遍历器 Iterator 对象的方法 next 方法 re ...

  2. es6 Generator函数的含义

    Generator函数的含义 Generator 与状态机 Generator 是实现状态机的最佳结构.比如,下面的clock函数就是一个状态机. var ticking = true; var cl ...

  3. es6 Generator函数的this

    Generator函数的this Generator函数总是返回一个遍历器,ES6 规定这个遍历器是Generator函数的实例,也继承了Generator函数的prototype对象上的方法. fu ...

  4. es6 Generator函数概述

    概述 什么是Generator函数 Generator函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator函数的语法和 API,它的异步编程应用请看 ...

  5. ES6——Generator 函数的语法

    Generator 函数是一个状态机,封装了多个内部状态.执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对象 ...

  6. ES6 Generator函数

    一.基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同. Generator 函数有多种理解角度. 语法上,Generator 函数是一个状态机,封装 ...

  7. 理解 ES6 Generator 函数

    Generator函数是ES6引入的新型函数,用于异步编程,跟Promise对象联合使用的话会极大降低异步编程的编写难度和阅读难度. Generator函数跟普通函数的写法有非常大的区别: 一是,fu ...

  8. ES6 generator函数的详解

    迭代器的基础用法 function* helloGenerator(){yield '我是next1'yield '我是next2'return 123 } let res = helloGenera ...

  9. ES6 Generator函数 深入应用

    前提 线程与进程 在操作系统(此处说的系统是引入了线程概念的系统)中一个应用要想执行必须有一定的执行资源,而执行资源大致分为两个部分一个是执行时需要用的内存,一个是CPU执行权.而系统分配给每个应用的 ...

最新文章

  1. 64位汇编之linux系统调用
  2. centos7安装配置rsync以及遇到问题
  3. linux 编译 freescale arm 的gdb server
  4. C#起步:WinForm当中的字符
  5. 【Flink】Flink 1.9 写入 es6 案例
  6. vagrant启动报错The following SSH command responded with a no
  7. win7系统服务器错误404,Win7旗舰版系统下无法打开http://localhost出现404错误如何解决...
  8. php mysql日期转换成时间戳_php日期转时间戳,指定日期转换成时间戳【转】
  9. RESTClient 使用,一个小巧方便的插件
  10. Intel HM55 AHCI 驱动 安装指南
  11. 二维码生成器微信小程序源码
  12. 湖南科技大学c语言上机题库,2018年湖南科技大学计算机科学与工程院824C语言程序设计与数据结构综合之C程序设计考研核心题库...
  13. 二,变量(variable)
  14. python实现提取视频里的语音转换为文字
  15. mysql客户端如何登录_MySQL-客户端登录问题
  16. 服务器 硬件防火墙,了解服务器软硬件防火墙
  17. 看不见的共享电单车战争
  18. 职中计算机一级证,职中计算机等级一级考证教学网站的设计
  19. LC160. 相交链表
  20. qrect在图片上显示矩形框_2019年6月百度大脑产品上新技术升级盘点内容

热门文章

  1. python sqlite3使用详解
  2. .net页面生命周期
  3. 操作系统(6)-协程
  4. android apk 反编译工具,安卓apk反编译神器
  5. piccolo2d android,如何在Piccolo2D中打洞?
  6. hibernate连接mysql 释放连接_SSH 占用数据库连接不释放问题
  7. go + influxdb + grafana 日志监控系统
  8. LeetCode448-找到所有数组中消失的数字(原地数组)
  9. Bootstrap pc pad phone 响应式布局
  10. ubuntu远程访问摄像头的设置