关于ES6生成器,你应该知道的地方

摘自你不知道的JavaScript中卷


前言

通过前面一章的学习,我们知道了Promise为我们解决了回调带来的不信任问题以及基于回调的异步嵌套不符合人类大脑的规划方式,最终通过.then的方式来编写异步代码,即便是.then的形式,如果连续的编写也会带来另一种流程控制风格的问题,那么有没有其他更好的,看似同步的异步流程控制风格呢?

记得在一篇文章中看过,有人说最好的异步就是没有异步

Generator

ES6中引入的一种新的函数类型,这类函数不符合传统函数从运行到结束的特性,可中断

生成器函数如何定义
function *foo (x) {
    x++;
    let y = yield x;
    console.log(y);
}

可以看到,生成器函数在命名上比传统函数多了一个*号,且在方法体内有yield关键字

如何调用生成器函数
var foo = foo(2);// 1
var v = foo.next();// 2
console.log(v.value);// 3
foo.next(10);// 4
  1. 上面的第一步并没有执行生成器函数foo,而只是构造了一个迭代器Iterator,通过这个迭代器实例来控制生成器函数的执行

  2. 中的第一次next()调用实际上是启动了生成器,并执行到第一个有yield表达式的地方暂停住,next()第一次调用结束,此时*foo()仍在运行并且时活跃的状态,只是其处于暂停状态,

  3. 注意这里2中调用后的返回值v,其实际上表示的是*foo()函数体内yield关键字后面的值的封装,为什么说是值得封装,因为next()函数总是返回一个对象,对象中包含两个属性,一个value,一个done

value就是yield关键字后面表达式的值,而done表示的是foo迭代器是否迭代完成的标识:true或false

  1. 第四步中next()是可以传参的,由于之前的执行*foo()暂停在了第一个yield关键字的位置,故第四步中的next传参实际上是将传参的值赋值给了那第一个yield关键字

至此我们知道了ES6生成器可以通过返回的迭代器实例来自由控制其执行流程,并可以通过yield关键字实现双向数据传输(即通过生成器暴露值出来以及向生成器函数传值进去)

代码执行结果

3
10
{value: undefined, done: true}

异步迭代生成器

明白了生成器的执行流程,那么我们将异步加入生成器中看看效果,直接上代码

// 定义一个函数,模拟异步请求返回
function timeout () {
    setTimeout(() => {
        console.log('耗时三秒模拟异步请求');
        it.next({data: 'success', code: 0});
    }, 3000);
}

// 定义一个生成器函数
function *main() {
    var value = yield timeout();
    console.log(value); 
}

// 启动生成器
var it = main();
it.next();

下面分析一下以上代码的意图以及流程

意图:

  1. 定义的timeout函数一般就是我们实际项目中请求后端数据接口的代码,在ajax请求回调函数成功时将后端数据传入迭代器的next()中

  2. *main生成器函数一般是我们在实际项目中调用封装了ajax请求函数的客户端代码片段

  3. 启动生成器函数

流程:

  1. 调用生成器函数main返回一个迭代器实例it

  2. 启动迭代器

  3. 迭代器执行timeout()函数,并暂停在yield表达式处

  4. timeout函数执行,等待3秒后执行打印'耗时三秒模拟异步请求',并继续执行迭代器的next方法,并向其传入参数:{data: 'success', code: 0}

  5. 迭代器从yield处激活重新执行,并打印传入的参数对象,执行流程结束

大家重点看一下这里的代码片段

var value = yield timeout();
console.log(value); 

timeout函数是一个模拟请求远程的异步方法,这里用了两行代码,就获取到了远程调用的结果值,而且是看似同步的形式获取的

这不就是我们说的好的异步就是没有异步的概念吗?

而且最重要的一点是以上代码并没有阻塞整个程序代码,而只是阻塞或暂停了生成器本身

通过以上的小例子,我相信你已经明白了生成器函数存在的意义了

生成器函数的意义

我们拥有了在生成器函数内部看似完全同步的代码逻辑,且yield背后的表达式内部可以完全异步形式调用

借用书中的一句话:

从本质上来说,我们把异步作为实现细节抽象了出去,使得我们可以以同步顺序的形式追踪流程控制:

发出一个Ajax请求,等他完成之后打印出相应结果


生成器 + Promise组合

ES6中最完美的世界就是生成器(看似同步的异步代码)和 Promise(可信任可组合)的组合

请好好理解以上这句话的意义所在,这是目前ES6中最精华的一句话

以上代码示例中,我们利用了生成器去yield一个异步Ajax请求,而Ajax回调存在的问题就是可信任问题,为了解决可信任的问题,我们需要利用Promise

改进
  1. 让封装了ajax请求的方法返回一个Promise,且不需要在原代码中控制迭代器的next(),仅仅只是返回一个Promise即可

  2. 迭代器内部代码不需要做任何的变动,也就是说yield关键字后面的表达式将会暴露出一个Promise对象出来

  3. 启动迭代器的代码变动:

     var it = main();
     var p = it.next().value;
     // 此处编写Promise的决议后回调代码
     p.then(function fulfilled (text) {
         // 将Promise决议完成的后端结果传入迭代器的第二个next()中
         it.next(text);
     }, function rejected(error) {
         it.throw(error);
     });
    

通过以上的改进,我们可以看到,加入了Promise的代码解决了回调的可信任问题,且生成器函数内部代码不需要做任何的改动即可

在生成器内部,不管yield出来的是什么值,都只是透明的,我们不需要关心它

通过yield暴露出来的Promise之后,我们应该对其做什么呢?

  • 对这个Promise做决议监听,在决议完成回调中恢复生成器的执行

  • 在决议拒绝回调中向生成器抛出一个带有拒绝原因的错误

再次改进

通过以上分析我们知道了,其实我们需要编写的代码部分只有:

  • 请求后端数据接口返回Promise实例方法

  • 定义一个生成器函数,用于在内部yield 出返回的Promise实例

  • 启动迭代器代码,对next()返回的Promise实例添加then回调函数,并在决议成功和决议拒绝时恢复生成器或抛给生成器一个错误

细细分析以上三个步骤,我们发现:第一步,我们是必须要编写的,关系到请求的后端接口

第二步中也是需要编写的,而第三步其实是业务无关的,也就是说我们可以编写出一个工具来做第三步中所做的工作,比如asynquence库及其runner(...)

ES7 async 和 await?

前面通过生成器yield出来一个Promise实例,然后利用Promise的决议监听去控制迭代器的执行,直到结束的形式是一种非常有用的方式,若我们能够无需使用工具辅助函数(run(...))就能够实现就好了

关于这一点,好消息是目前主流浏览器Chrom等已经强势支持了这一方面的增强语法形式async await

也就是我们平常在代码中编写的A/A形式代码

1:编写异步函数返回Promise实例

2:无需定义生成器形式函数,采用在函数前增加async关键字代替*号,以及在函数内部添加await关键字代替yield的形式来处理

3:不在需要yield出一个Promise实例后再去编写决议监听回调函数处理生成器的恢复执行或抛给生成器一个异常错误,而是利用更强大的await关键字直接等待1中Promise的决议

4:也就是说,如果你await了一个Promise, async 函数就会自动获知要做什么,它会暂停该函数(就像生成器暂停一样),直到该Promise决议

5:async函数在调用后会自动返回一个待决议的Promise,在函数完全结束之后,该Promise会自动决议
语法糖?

从本质上来说,async和await就是生成器加Promise的语法糖,将这两个ES6世界中最美好的部分结合起来,优雅并实际的解决了回调方案存在的主要问题

总结

以上就是本人对于ES6中生成器的一些简单理解,有错误的地方还请指出修正

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容