zoukankan      html  css  js  c++  java
  • Generator函数

    Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。

    一、语法

    二、API

    三、应用

    四、异步应用

    一、语法

    Generator函数是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,返回的遍历器对象可以依次遍历Generator函数内部的每一个状态。

    Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。

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

    上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(helloworld),即该函数有三个状态:hello,world 和 return 语句(结束执行)。

    调用 Generator 函数后,该函数并不执行,而是返回一个指向内部状态的指针对象。下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

    遍历器对象的next方法的运行逻辑如下。

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

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

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

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

    Generator函数可以不用yield表达式,就变成了一个单纯的暂缓执行函数。

    function* f() {
      console.log('执行了!')
    }
    
    var generator = f();
    
    setTimeout(function () {
      generator.next()
    }, 2000);
    
    //输出
    执行了!
    var arr = [1, [[2, 3], 4], [5, 6]];
    
    var flat = function* (a) {
      var length = a.length;
      for (var i = 0; i < length; i++) {
        var item = a[i];
        if (typeof item !== 'number') {
          yield* flat(item);
        } else {
          yield item;
        }
      }
    };
    
    for (var f of flat(arr)) {
      console.log(f);
    }
    
    //输出
    1
    2
    3
    4
    5
    6

    注意:

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

    2、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
    }

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

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

    任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数(Generator函数),调用该函数会返回该对象的一个遍历器对象。可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

    var myIterable = {};
    myIterable[Symbol.iterator] = function* () {
      yield 1;
      yield 2;
      yield 3;
    };
    
    [...myIterable] // [1, 2, 3]

    Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

    function* gen(){
      // some code
    }
    
    var g = gen();
    
    g[Symbol.iterator]() === g
    // true

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

    从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

    如果想要第一次调用next方法时,就能够输入值,可以在 Generator 函数外面再包一层。

    function wrapper(generatorFunction) {
      return function (...args) {
        let generatorObject = generatorFunction(...args);
        generatorObject.next();
        return generatorObject;
      };
    }
    
    const wrapped = wrapper(function* () {
      console.log(`First input: ${yield}`);
      return 'DONE';
    });
    
    wrapped().next('hello!')
    
    //输出
    First input: hello!
    {value: "DONE", done: true}

    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循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

    function* numbers () {
      yield 1
      yield 2
      return 3
      yield 4
    }
    
    // 扩展运算符
    [...numbers()] // [1, 2]
    
    // Array.from 方法
    Array.from(numbers()) // [1, 2]
    
    // 解构赋值
    let [x, y] = numbers();
    x // 1
    y // 2
    
    // for...of 循环
    for (let n of numbers()) {
      console.log(n)
    }
    // 1
    // 2

    二、API

    1、Generator.prototype.throw()

    Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

    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

    上面代码中,遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。

    2、Generator.prototype.return()

    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 }

    上面代码中,遍历器对象g调用return方法后,返回值的value属性就是return方法的参数foo。并且,Generator 函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性总是返回true

    如果return方法调用时,不提供参数,则返回值的value属性为undefined

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

    3、next()、throw()、return()的共同点

    next()throw()return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

    next()是将yield表达式替换成一个值。

    const g = function* (x, y) {
      let result = yield x + y;
      return result;
    };
    
    const gen = g(1, 2);
    gen.next(); // Object {value: 3, done: false}
    
    gen.next(1); // Object {value: 1, done: true}
    // 相当于将 let result = yield x + y
    // 替换成 let result = 1;

    上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined

    throw()是将yield表达式替换成一个throw语句。

    gen.throw(new Error('出错了')); // Uncaught Error: 出错了
    // 相当于将 let result = yield x + y
    // 替换成 let result = throw(new Error('出错了'));

    return()是将yield表达式替换成一个return语句。

    gen.return(2); // Object {value: 2, done: true}
    // 相当于将 let result = yield x + y
    // 替换成 let result = return 2;

    yield* 表达式???解决在一个Generator函数里面执行另一个Generator函数。foobar都是 Generator 函数,在bar里面调用foo,就需要手动遍历foo。如果有多个 Generator 函数嵌套,写起来就非常麻烦。

    function* foo() {
      yield 'a';
      yield 'b';
    }
    
    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"
    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"

    如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*表达式。

    yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。

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

    在有return语句时,则需要用var value = yield* iterator的形式获取return语句的值。

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

    let read = (function* () {
      yield 'hello';
      yield* 'hello';
    })();
    
    read.next().value // "hello"
    read.next().value // "h"

    yield*命令可以很方便地取出嵌套数组的所有成员。

    function* iterTree(tree) {
      if (Array.isArray(tree)) {
        for(let i=0; i < tree.length; i++) {
          yield* iterTree(tree[i]);
        }
      } else {
        yield tree;
      }
    }
    
    const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
    
    for(let x of iterTree(tree)) {
      console.log(x);
    }
    
    // a
    // b
    // c
    // d
    // e

    等价于

    [...iterTree(tree)] 
     // ["a", "b", "c", "d", "e"]

    三、应用

    1、异步操作的同步化表达

    2、控制流管理

    3、部署 Iterator 接口 

    4、作为数据结构

    四、异步应用

    ES6 诞生以前,异步编程的方法,大概有下面四种。

    • 回调函数
    • 事件监听
    • 发布/订阅
    • Promise 对象
  • 相关阅读:
    安装rabbitmq以及python调用rabbitmq--暂欠
    nginx报错:No package erlang available
    zipimport.ZipImportError: can't decompress data; zlib not available 解决办法
    centos python2.6升级到2.7 还有单独的python3.5环境
    TypeError: datetime.datetime(2016, 9, 25, 21, 12, 19, 135649) is not JSON serializable解决办法(json无法序列化对象的解决办法)
    django的跨站请求访问
    django的中间件
    django缓存
    django的分页--不全也未实现
    django的cookie 和session
  • 原文地址:https://www.cnblogs.com/songya/p/11694444.html
Copyright © 2011-2022 走看看