• Generator与async/await与Generator的模拟


    Generator

    函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

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

    形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    

    下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

    hw.next()
    // { value: 'hello', done: false }
    hw.next()
    // { value: 'world', done: false }
    hw.next()
    // { value: 'ending', done: true }
    hw.next()
    // { value: undefined, done: true }
    

    next 方法的参数

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

    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{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 }
    

    for...of 循环

    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);
    }
    

    基于 Promise 对象的自动执行

    首先,把fs模块的readFile方法包装成一个 Promise 对象

    var fs = require('fs');
    
    var readFile = function (fileName){
      return new Promise(function (resolve, reject){
        fs.readFile(fileName, function(error, data){
          if (error) return reject(error);
          resolve(data);
        });
      });
    };
    
    var gen = function* (){
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    

    然后,手动执行上面的 Generator 函数。

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

    手动执行其实就是用then方法,层层添加回调函数。理解了这一点,就可以写出一个自动执行器。一般使用模块。

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

    上面代码中,只要 Generator 函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。

    async/await

    async 函数是什么?一句话,它就是 Generator 函数的语法糖。
    async函数对 Generator 函数的改进,体现在以下四点。
    (1)内置执行器。

    Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

    (2)更好的语义。

    async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

    (3)更广的适用性。

    co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

    (4)返回值是 Promise。

    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

    async 函数的实现原理

    async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

    async function fn(args) {
      // ...
    }
    
    // 等同于
    
    function fn(args) {
      return spawn(function* () {
        // ...
      });
    }
    

    所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。

    下面给出spawn函数的实现,基本就是前文自动执行器的翻版。

    function spawn(genF) {
      return new Promise(function(resolve, reject) {
        const gen = genF();
        function step(nextF) {
          let next;
          try {
            next = nextF();
          } catch(e) {
            return reject(e);
          }
          if(next.done) {
            return resolve(next.value);
          }
          Promise.resolve(next.value).then(function(v) {
            step(function() { return gen.next(v); });
          }, function(e) {
            step(function() { return gen.throw(e); });
          });
        }
        step(function() { return gen.next(undefined); });
      });
    }
    

    模拟

    生成器是通过暂停自己的作用域 / 状态实现它的“魔法”的。可以 通过函数闭包

    • 1 是起始状态
    • 2 是 request(..) 成功后的状态
    • 3 是 request(..) 失败的状态
    function foo(url) {
      var state;//管理状态
      var val;//生成器变量范围声明
    
      function process(v) {
        switch (state) {
          case 1:
            return request(url);
          case 2:
            val = v;
            return;
          case 3:
            var err = v;
            console.log( "Oops:", err );
            return false;
        }    
     // 构造并返回一个生成器 
     return {
        next: function(v) { // 初始状态
             if (!state) {
                 state = 1;
                 return {
                     done: false,
                     value: process()
                 };
                }
            // yield成功恢复
            else if (state == 1) {
                 state = 2;
                 return {
                        done: true,
                        value: process( v )
                    };
            }
            // 生成器已经完成 
            else {
                 return {
                     done: true,
                     value: undefined
                 };
            } },
            "throw": function(e) {
            // 唯一的显式错误处理在状态1
            if (state == 1) {
                 state = 3;
                 return {
                     done: true,
                     value: process( e )
                     };
            }
                // 否则错误就不会处理,所以只把它抛回 else {
            throw e; }
        } };
    }
    

    这段代码是如何工作的呢?

    • (1) 对迭代器的 next() 的第一个调用会把生成器从未初始化状态转移到状态 1,然后调用 process() 来处理这个状态。request(..) 的返回值是对应 Ajax 响应的 promise,作为 value 属性从 next() 调用返回。
    • (2) 如果 Ajax 请求成功,第二个 next(..) 调用应该发送 Ajax 响应值进来,这会把状态转 移到状态 2。再次调用 process(..)(这次包括传入的 Ajax 响应值),从 next(..) 返回 的 value 属性将是 undefined。
    • (3) 然而,如果 Ajax 请求失败的话,就会使用错误调用 throw(..),这会把状态从 1 转移到 3(而非 2)。再次调用 process(..),这一次包含错误值。这个 case 返回 false,被作 为 throw(..) 调用返回的 value 属性。

    从外部来看(也就是说,只与迭代器交互),这个普通函数 foo(..) 与生成器 *foo(..) 的 工作几乎完全一样。所以我们已经成功地把 ES6 生成器转为了前 ES6 兼容代码!
    然后就可以手工实例化生成器并控制它的迭代器了,调用var it = foo("..")和 it.next(..) 等。甚至更好的是,我们可以把它传给前面定义的工具 run(..),就像 run(foo,"..")。

    regenerator 就 是 这 样 的 一 个 工 具(http://facebook.github.io/regenerator/), 出 自 Facebook 的 几个聪明人。
  • 相关阅读:
    OCP-1Z0-053-200题-54题-679
    OCP-1Z0-053-200题-55题-632
    OCP-1Z0-053-200题-78题-655
    底层框架PhoneGap
    用Dw CS6运行静态页面出问题
    JavaScript split()函数
    Java Web项目报错总结
    dojo报错总结
    FusionCharts中图的属性的总结归纳
    dojo表格的一些属性
  • 原文地址:https://www.cnblogs.com/chenjinxinlove/p/8467774.html
Copyright © 2020-2023  润新知