ES6必知必会 (七)—— Generator 函数
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()// 隐藏loadingloader.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 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行,另外,它的函数体内外的数据交换和错误处理机制的特点使它可以作为异步编程的完整解决方案;
转载于:https://www.cnblogs.com/wntd/p/9013316.html
ES6必知必会 (七)—— Generator 函数相关推荐
- 《SQL必知必会》第十三课 创建高级联结表 使用不同类型的联结 使用带聚集函数的联结 使用联结时应注意的问题
第十三课 创建高级联结表 #使用表别名的原因 #不同的联结类型以及每类联结所使用的语法 #如何与联结一起使用聚集函数 #使用联结时的注意问题 一.使用表别名 [1]前面(第七课)介绍使用别名引用被检索 ...
- c2064 项不会计算为接受0个参数的函数_【JS必知必会】高阶函数详解与实战
本文涵盖 前言 高级函数概念 函数作为参数的高阶函数 map filter reduce sort详解与实战 函数作为返回值的高阶函数 isType函数与add求和函数 如何自己创建高阶函数 前言 一 ...
- mysql函桌为之一的_MYSQL必知必会读书笔记第十和十一章之使用函数处
mysql简介 MySQL是一种开放源代码的关系型数据库管理系统(RDBMS),MySQL数据库系统使用最常用的数据库管理语言--结构化查询语言(SQL)进行数据库管理. 拼接字段 存储在数据库表中的 ...
- GitHub#SQL#:SQL必知必会
https://github.com/CyC2018/Interview-Notebook 一.基础 二.创建表 三.修改表 四.插入 五.更新 六.删除 七.查询 八.排序 九.过滤 十.通配符 十 ...
- SQL Server必知必会
SQL Server必知必会 2009-10-27-17:57:57 Structure Query Language:SQL 结构化 查询 语言 数据库产品: ...
- C++必知必会RALL与Pimpl
系列文章目录 第一章 C++必知必会 第二章 C++后端开发必备的工具和调试知识 第三章 多线程编程与资源同步 第四章 网络编程重难点解析 第五章 网络通信故障排除常用命令 第六章 网络通信协议设计 ...
- 根据SQL必知必会学习SQL(MYSQL)
很久都没有整理SQL语句了,遇到让写SQL语句的题也很迷茫,所以就重拾一下知识,本文章根据SQL必知必会进行梳理 文章目录 一.检索所有列 1.select 1.1检索单个列 1.2 检索多个列 1. ...
- MySQL必知必会笔记(一)基础知识和基本操作
第一章 了解MySQL 数据库 保存有组织的数据的容器.(通常是一个文件或一组文件) 人们经常使用数据库这个术语代替他们使用的软件.这是不正确的,确切的说,数据库软件应称为DBM ...
- MySQL必知必会pdf网盘下载+每章总结
书籍下载 链接:https://pan.baidu.com/s/18g0XgavniXQzp6RlOxnwnQ 密码:j8pm 目录 书籍下载 MySQL必知必会 第二章 连接mysql 第三章 连接 ...
- 读书笔记系列1——MySQL必知必会
读书笔记系列1--MySQL必知必会 文章目录 读书笔记系列1--MySQL必知必会 MySQL官方文档:https://dev.mysql.com/doc/ 第一章 数据库基础 *2021.11.2 ...
最新文章
- 【转】java读写二进制文件的解决方法
- 1874畅通工程续(dijkstra算法)
- java文件头_对java文件头的解析
- 第一次搭建阿里云服务器
- BeanDefinitionRegistryPostProcessor​ 的处理源码流程
- Delphi应用程序在命令行下带参数执行返回命令行提示的问题
- java ssh 和mvc_[转]JAVA三大框架SSH和MVC
- java ojdbc 还需要装 oracle client 吗,ojdbc连接数据库
- wordpress主页php,wordpress主页在哪里设置
- linux驱动编写(虚拟字符设备编写)
- long到number转换 ts_js如何将纯数字字符串转换为long型
- [codevs 1503]愚蠢的宠物(特殊的LCA)
- android mvp框架基类,Android MVP架构模式基类封装
- linux获取脚本文件路径
- Pygame实战之外星人入侵NO.7——大批外星人来袭
- 基于ibeacons三点定位(微信小程序)
- HTML5 混合APP开发学习笔记(三)——CSS样式设计
- 【docker安装】docker安装与部署
- window安装weblogic教程
- android 系统 dpi设置,安卓系统DPI的划分
热门文章
- vue里写三元判断绑定class和style
- 前端学习(3290):react hook useReducer传入对象+1操作继续编辑更多
- 前端学习(3275):js中this的使用四
- 前端学习(3273):js中this的使用二
- [html]html实现页面跳转都有哪些方法?
- [js] AudioContext有什么应用场景?
- 前端学习(2731):重读vue电商网站41之自定义格式化时间的全局过滤器
- 前端学习(2658):vue3 computed
- 前端学习(2230):react条件渲染实现登录二
- 前端学习(2043)vue之电商管理系统电商系统之优化web.config.js的配置文件