zoukankan      html  css  js  c++  java
  • ES6异步方案——生成器Generators/yield

    一、Generator函数简介

      generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。

      ES6定义generator标准时借鉴了Python的generator的概念和语法。

    1、理解Generator函数

      Generator函数有多种理解角度。

    function* gen() { 
      yield 1;
      yield 2;
      yield 3;
      return 'ending';
    }
    
    let g = gen(); 
    // "Generator { }"

      上面代码定义了一个Generator函数 gen,它内部有三个yield 表达式,即该函数有四个状态:1,2,3和return语句(结束执行)。

    (1)语法

      首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象

      也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

    (2)形式

      Generator 函数是一个普通函数,但是有两个特征。

      一是,function关键字与函数名之间有一个星号

      二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

     2、next方法

      Generator.prototype.next()方法用于恢复执行,返回值是包含value和done属性的对象。

      调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。

      因此必须调用遍历器对象的next方法,使得指针移动向下一个状态。

      每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

      换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行

    function* gen() {
        yield 1;
        yield 2;
        yield 3;
        return 'ending';
    }
    
    let g = gen();
    // "Generator { }"
    
    console.log(g.next());    // { value: 1, done: false }
    console.log(g.next());    // { value: 2, done: false }
    console.log(g.next());    // { value: 3, done: false }
    console.log(g.next());    // { value: 'ending', done: true }

      上面的代码中调用了四次next方法。第一次调用,Gererator函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,value属性是当前yield表达式的值,done属性值为false,表示遍历未结束。

      第二次、第三次调用则是从上次yield表达式停止的地方开始,一直执行到下一个yield表达式。

      第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefineddone属性为true。以后再调用next方法,返回的都是这个值。

    3、return方法

      Generator.prototype.return()方法用于立即结束遍历,并返回给定的值。

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

      Generator函数的返回值是遍历器对象,但可以用return方法指定返回的值。参数就是返回值的value属性。使用return方法后,done属性将设为true,立即终结遍历Generator函数。

      注意:
      1)如果遍历尚未结束,即done为false的情况下,调用无参的return(),会将value设为undefined。
      2)如果遍历已经结束,即done已经为true的情况下,调用return(value)是没有意义的,参数也不会生效,value会固定为undefined。

    4、Generator调用

      调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

      ES6 没有规定,function关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。

    function * foo(x, y) { ··· }
    function *foo(x, y) { ··· }
    function* foo(x, y) { ··· }
    function*foo(x, y) { ··· }

      由于 Generator 函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在function关键字后面。

    5、yield表达式

      Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

    (1)next 方法运行逻辑

      1)遇到 yield 表达式,则暂停执行后面的操作,并紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value 属性值。

      2)下次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 表达式。

      3)若没有再遇到新的 yield 表达式,则一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value 属性值。

      4)若该函数没有 return 语句,则返回的对象的 value 属性值为 undefined

    (2)yield 与 return 对比

      yield表达式与return语句既有相似之处,也有区别。

      相似处:都能返回紧跟在语句后面的那个表达式的值。

      不同处:

    • 每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行;return 语句不具备位置记忆的功能
    • 一个函数里面,可以执行多次(或者说多个)yield表达式;只能执行一次(或者说一个)return语句
    • Generator 函数可以返回一系列的值,因为可以有任意多个yield正常函数只能返回一个值,因为只能执行一次return

    二、Generator函数异步应用

      由于Generator函数可以交出函数的执行权,整个Generator函数可以看作一个封装的异步任务,或者说是异步任务的容器

      异步操作需要暂停的地方,都用 yield 语句注明。Generator 函数的执行方法如下:

    function* gen(x) {
      var y = yield x + 2;
      return y;
    }
    
    var g = gen(1);
    g.next() // { value: 3, done: false }
    g.next() // { value: undefined, done: true }

      调用函数,返回一个内部指针(遍历器)g

    1、Generator数据交换和错误处理

      Generator 函数可以暂停执行恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换错误处理机制

    (1)next实现函数体内外数据交换

      next返回值的 value 属性,是 Generator 函数向外输出数据;next方法还可以接受参数,向 Generator 函数体内输入数据。

    function* gen(x){
      var y = yield x + 2;
      return y;
    }
    
    var g = gen(1);
    g.next() // { value: 3, done: false }
    g.next(2) // { value: 2, done: true }

      第一个next方法的value属性,返回表达式x + 2的值3

      第二个next方法带有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量y接收。因此,这一步的value属性,返回的就是2(变量y的值)。

    (2)函数内部错误处理

      Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。

    function* gen(x){
      try {
        var y = yield x + 2;
      } catch (e){
        console.log(e);
      }
      return y;
    }
    
    var g = gen(1);
    g.next();
    g.throw('出错了');

      Generator 函数体外,使用指针对象的throw方法抛出的错误,可以被函数体内的try...catch代码块捕获。

      这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。

    2、异步任务封装

    var fetch = require('node-fetch');
    
    function* gen(){
      var url = 'https://api.github.com/users/github';
      var result = yield fetch(url);
      console.log(result.bio);
    }

      上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。

      这段代码非常像同步操作,除了加上了yield命令。

      执行这段代码:

    var g = gen();
    var result = g.next();
    
    result.value.then(function(data){
      return data.json();
    }).then(function(data){
      g.next(data);
    });

      首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。

      由此可见,虽然Generator函数将异步操作表达很简洁,但流程管理不便。

  • 相关阅读:
    页面性能优化之内容加载优化(转)
    (Windbg调试六)c++句柄泄漏问题定位
    (Windbg调试五)C++内存越界导致的std::map异常
    (Windbg调试四)C++死锁问题定位与分析
    (Windbg调试三)C++ delete指针后依然可以访问的问题
    (Windbg调试二)Windows下c++程序崩溃问题定位
    (Windbg调试一)minidump崩溃捕捉
    WinDbg调试:配置和查看符号
    为WinDbg设置符号文件路径
    Windbg符号与源码 《第二篇》
  • 原文地址:https://www.cnblogs.com/xiugeng/p/12782255.html
Copyright © 2011-2022 走看看