zoukankan      html  css  js  c++  java
  • ES6 (10):Generator

    异步编程方案:(很实用滴)

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

    每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行

    yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

    var arr = [1, [[2, 3], 4], [5, 6]];
    
    var flat = function* (a) {
      a.forEach(function (item) {
        if (typeof item !== 'number') {
          yield* flat(item);
        } else {
          yield item;
        }
      });
    };
    
    for (var f of flat(arr)){
      console.log(f);
    }
    

     因为是在forEach中(forEach 本身是一个函数,所以会报错)

    yield表达式如果用在另一个表达式之中,必须放在圆括号里面。

    function* demo() {
      console.log('Hello' + yield); // SyntaxError
      console.log('Hello' + yield 123); // SyntaxError
    
      console.log('Hello' + (yield)); // OK
      console.log('Hello' + (yield 123)); // OK
    }
    

     yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

    function* demo() {
      foo(yield 'a', yield 'b'); // OK
      let input = yield; // OK
    }
    

    next方法参数:next方法可以传入一个参数,这个参数会被当做上一次yield执行时的初始值(如果不传参数,默认是undefined

    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 }
    

    主要看第一个实例(b),第一次调用next此次yield 后的值为6,第二次执行next时,此时设置yield为12,也就是上面我标红的地方,(yield (x + 1)) = 12, 所以y = 24,到第二行yield 时,此时yield 回传为8,当调用第三次next方法时,设置值为13,标红的地方 yield (y / 3) = 13, 所以z值为13,然后到达return ,此时 上面执行结果就是,x 为5,y为24,z为13,相加得到42。

    注意如果yield 后面是一个表达式,那么初始值代表的是这个表达式的值,也就是yield紧跟着的。而上面的实例a因为在第二次调用next方法的时候没有传参,所以yield 的值默认为undefined,所以值为NaN

    for...of : 因为Generator 运行时会生成iterator,所以可以直接遍历

    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
    

    遍历对象:

    function* objectEntries(obj) {
      let propKeys = Reflect.ownKeys(obj);// 获取对象的keys
    
      for (let propKey of propKeys) {// 循环执行,返回key与value
        yield [propKey, obj[propKey]];
      }
    }
    
    let jane = { first: 'Jane', last: 'Doe' };
    
    for (let [key, value] of objectEntries(jane)) {
      console.log(`${key}: ${value}`);
    }
    

     因为本身就会生成一个iterator 对象,所以可以直接赋值到对象的Symbol.iterator接口上

    function* objectEntries() {
      let propKeys = Object.keys(this);
    
      for (let propKey of propKeys) {
        yield [propKey, this[propKey]];
      }
    }
    
    let jane = { first: 'Jane', last: 'Doe' };
    
    jane[Symbol.iterator] = objectEntries;
    
    for (let [key, value] of jane) {
      console.log(`${key}: ${value}`);
    }
    // first: Jane
    // last: Doe
    

    x.throw:抛出异常(记得用try catch捕获)

    var g = function* () {
      try {
        yield;
      } catch (e) {
        console.log('内部捕获', e);
      }
    };
    
    var i = g();
    i.next();
    
    try {
      i.throw('a');
      i.throw('b');
    } catch (e) {
      console.log('外部捕获', e);
    }
    // 内部捕获 a
    // 外部捕获 b
    

     catch只会捕获一次,并且返回捕获后下一次的yield 值

    如果yield 都在try catch中,那么不会返回。

     没有返回2,因为yield 2 在try catch内(并且不会影响下一次的遍历上例中在错误后继续遍历,输出了4)。

    Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获。

    一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefineddone属性等于true的对象,即 JavaScript 引擎认为这个 Generator 已经运行结束了。

    return:终结遍历Generator

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

    yield* 表达式:在 Generator 函数内部,调用另一个 Generator 函数。

    对比:

    function* bar() {
      yield 'x';
      yield* foo();
      yield 'y';
    }
    
    // 等同于
    function* bar() {
      yield 'x';
      yield 'a';
      yield 'b';
      yield 'y';
    }
    
    // 等同于
    function* bar() {
      yield 'x';
      for (let v of foo()) {
        yield v;
      }
      yield 'y';
    }
    
    for (let v of bar()){
      console.log(v);
    }
    // "x"
    // "a"
    // "b"
    // "y"
    

     另外因为Generator 会生成一个遍历器对象,所以,会有下面这样的不同

    function* inner() {
      yield 'hello!';
    }
    
    function* outer1() {
      yield 'open';
      yield inner();
      yield 'close';
    }
    
    var gen = outer1()
    gen.next().value // "open"
    gen.next().value // 返回一个遍历器对象
    gen.next().value // "close"
    
    function* outer2() {
      yield 'open'
      yield* inner()
      yield 'close'
    }
    
    var gen = outer2()
    gen.next().value // "open"
    gen.next().value // "hello!"
    gen.next().value // "close"
    

     out1 在执行到第二行的时候返回的是一个遍历器对象。

    任何数据结构只要有 Iterator 接口,就可以被yield*遍历

    yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。(如果有return,那么return的值不会被for ...of 侦测到,但是可以作为想代理它的Generator 函数返回数据。)

    function* concat(iter1, iter2) {
      yield* iter1;
      yield* iter2;
    }
    
    // 等同于
    
    function* concat(iter1, iter2) {
      for (var value of iter1) {
        yield value;
      }
      for (var value of iter2) {
        yield value;
      }
    }
    

     

    function* foo() {
      yield 2;
      yield 3;
      return "foo";
    }
    
    function* bar() {
      yield 1;
      var v = yield* foo();
      console.log("v: " + v);
      yield 4;
    }
    
    var it = bar();
    
    it.next()
    // {value: 1, done: false}
    it.next()
    // {value: 2, done: false}
    it.next()
    // {value: 3, done: false}
    it.next();
    // "v: foo"
    // {value: 4, done: false}
    it.next()
    // {value: undefined, done: true}
    

    对象属性:

    let obj = {
      * myGeneratorMethod() {
        ···
      }
    };
    let obj = {
      myGeneratorMethod: function* () {
        // ···
      }
    };

    协程:相比线程,由于 JavaScript 是单线程语言,只能保持一个调用栈。引入协程以后,每个任务可以保持自己的调用栈。这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈。不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束。

    如果将 Generator 函数当作协程,完全可以将多个需要互相协作的任务写成 Generator 函数,它们之间使用yield表达式交换控制权。同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配

     (也就是说可以自己控制)。

    异步操作同步化表达:Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

    function* loadUI() {
      showLoadingScreen();
      yield loadUIDataAsynchronously();
      hideLoadingScreen();
    }
    var loader = loadUI();
    // 加载UI
    loader.next()
    
    // 卸载UI
    loader.next()
    

     不用回调函数一个一个套了,看着多清晰~~

    控制流:

    一般如果异步操作,我们以前会这样做,一直套

    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
      })
      .done();
    

     已经很清晰了。

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

    更加趋于函数式编程。

    成灰之前,抓紧时间做点事!!
  • 相关阅读:
    小米笔试题:无序数组中最小的k个数
    搜狐畅游笔试题:1. 美丽的项链(动态规划) 2.多线程并发交替输出
    RPC系列:基本概念
    度小满面试题20190923
    Java 基础系列:异常
    JAVA基础系列:ThreadLocal
    leetcode 410. 分割数组的最大值(二分法)
    JAVA基础系列:Object类
    Redis系列1——概述
    剑指offer:对称的二叉树(镜像,递归,非递归DFS栈+BFS队列)
  • 原文地址:https://www.cnblogs.com/jony-it/p/10972892.html
Copyright © 2011-2022 走看看