zoukankan      html  css  js  c++  java
  • ES6笔记(5)-- Generator生成器函数

    系列文章 -- ES6笔记系列

    接触过Ajax请求的会遇到过异步调用的问题,为了保证调用顺序的正确性,一般我们会在回调函数中调用,也有用到一些新的解决方案如Promise相关的技术。

    在异步编程中,还有一种常用的解决方案,它就是Generator生成器函数。顾名思义,它是一个生成器,它也是一个状态机,内部拥有值及相关的状态,生成器返回一个迭代器Iterator对象,我们可以通过这个迭代器,手动地遍历相关的值、状态,保证正确的执行顺序。

    一、简单使用

    1. 声明

    Generator的声明方式类似一般的函数声明,只是多了个*号,并且一般可以在函数内看到yield关键字

    function* showWords() {
        yield 'one';
        yield 'two';
        return 'three';
    }
    
    var show = showWords();
    
    show.next() // {done: false, value: "one"}
    show.next() // {done: false, value: "two"}
    show.next() // {done: true, value: "three"}
    show.next() // {done: true, value: undefined}

    如上代码,定义了一个showWords的生成器函数,调用之后返回了一个迭代器对象(即show)

    调用next方法后,函数内执行第一条yield语句,输出当前的状态done(迭代器是否遍历完成)以及相应值(一般为yield关键字后面的运算结果)

    每调用一次next,则执行一次yield语句,并在该处暂停,return完成之后,就退出了生成器函数,后续如果还有yield操作就不再执行了

    2. yield和yield*

    有时候,我们会看到yield之后跟了一个*号,它是什么,有什么用呢?

    类似于生成器前面的*号,yield后面的星号也跟生成器有关,举个大栗子:

    function* showWords() {
        yield 'one';
        yield showNumbers();
        return 'three';
    }
    
    function* showNumbers() {
        yield 10 + 1;
        yield 12;
    }
    
    var show = showWords();
    show.next() // {done: false, value: "one"}
    show.next() // {done: false, value: showNumbers}
    show.next() // {done: true, value: "three"}
    show.next() // {done: true, value: undefined}

    增添了一个生成器函数,我们想在showWords中调用一次,简单的 yield showNumbers()之后发现并没有执行函数里面的yield 10+1

    因为yield只能原封不动地返回右边运算后值,但现在的showNumbers()不是一般的函数调用,返回的是迭代器对象

    所以换个yield* 让它自动遍历进该对象

    function* showWords() {
        yield 'one';
        yield* showNumbers();
        return 'three';
    }
    
    function* showNumbers() {
        yield 10 + 1;
        yield 12;
    }
    
    var show = showWords();
    show.next() // {done: false, value: "one"}
    show.next() // {done: false, value: 11}
    show.next() // {done: false, value: 12}
    show.next() // {done: true, value: "three"}

    要注意的是,这yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错

    function showWords() {
        yield 'one'; // Uncaught SyntaxError: Unexpected string
    }

    虽然换成yield*不会直接报错,但使用的时候还是会有问题,因为’one'字符串中没有Iterator接口,没有yield提供遍历

    function showWords() {
        yield* 'one'; 
    }
    
    var show = showWords();
    
    show.next() // Uncaught ReferenceError: yield is not defined

    在爬虫开发中,我们常常需要请求多个地址,为了保证顺序,引入Promise对象和Generator生成器函数,看这个简单的栗子:

    var urls = ['url1', 'url2', 'url3'];
    
    function* request(urls) {
        urls.forEach(function(url) {
            yield req(url);
        });
    
    //     for (var i = 0, j = urls.length; i < j; ++i) {
    //         yield req(urls[i]);
    //     }
    }
    
    var r = request(urls);
    r.next();
    
    function req(url) {
        var p = new Promise(function(resolve, reject) {
            $.get(url, function(rs) {
                resolve(rs);
            });
        });
    
        p.then(function() {
            r.next();
        }).catch(function() {
    
        });
    }

    上述代码中forEach遍历url数组,匿名函数内部不能使用yield关键字,改换成注释中的for循环就行了

    3. next()调用中的传参

    参数值有注入的功能,可改变上一个yield的返回值,如

    function* showNumbers() {
        var one = yield 1;
        var two = yield 2 * one;
        yield 3 * two;
    }
    
    var show = showNumbers();
    
    show.next().value // 1
    show.next().value // NaN
    show.next(2).value // 6

    第一次调用next之后返回值one为1,但在第二次调用next的时候one其实是undefined的,因为generator不会自动保存相应变量值,我们需要手动的指定,这时two值为NaN,在第三次调用next的时候执行到yield 3 * two,通过传参将上次yield返回值two设为2,得到结果

    另一个栗子:

    由于ajax请求涉及到网络,不好处理,这里用了setTimeout模拟ajax的请求返回,按顺序进行,并传递每次返回的数据

     1 var urls = ['url1', 'url2', 'url3'];
     2 
     3 function* request(urls) {
     4     var data;
     5 
     6     for (var i = 0, j = urls.length; i < j; ++i) {
     7         data = yield req(urls[i], data);
     8     }
     9 }
    10 
    11 var r = request(urls);
    12 r.next();
    13 
    14 function log(url, data, cb) {
    15     setTimeout(function() {
    16         cb(url);
    17     }, 1000);
    18     
    19 }
    20 
    21 
    22 function req(url, data) {
    23     var p = new Promise(function(resolve, reject) {
    24         log(url, data, function(rs) {
    25             if (!rs) {
    26                 reject();
    27             } else {
    28                 resolve(rs);
    29             }
    30         });
    31     });
    32 
    33     p.then(function(data) {
    34         console.log(data);
    35         r.next(data);
    36     }).catch(function() {
    37         
    38     });
    39 }

    达到了按顺序请求三个地址的效果,初始直接r.next()无参数,后续通过r.next(data)将data数据传入

    注意代码的第16行,这里参数用了url变量,是为了和data数据做对比

    因为初始next()没有参数,若是直接将url换成data的话,就会因为promise对象的数据判断 !rs == undefined 而reject

    所以将第16行换成 cb(data || url);

    通过模拟的ajax输出,可了解到next的传参值,第一次在log输出的是 url = 'url1'值,后续将data = 'url1'传入req请求,在log中输出 data = 'url1'值

    4. for...of循环代替.next()

    除了使用.next()方法遍历迭代器对象外,通过ES6提供的新循环方式for...of也可遍历,但与next不同的是,它会忽略return返回的值,如

    function* showNumbers() {
        yield 1;
        yield 2;
        return 3;
    }
    
    var show = showNumbers();
    
    for (var n of show) {
        console.log(n) // 1 2
    }

    此外,处理for...of循环,具有调用迭代器接口的方法方式也可遍历生成器函数,如扩展运算符...的使用

    function* showNumbers() {
        yield 1;
        yield 2;
        return 3;
    }
    
    var show = showNumbers();
    
    [...show] // [1, 2, length: 2]

    5. 更多使用

    更多使用可参考 MDN - Generator

  • 相关阅读:
    每天干的啥?(2017.7)
    每天干的啥?(2017.6)
    每天干的啥?(2017.5)
    每天干的啥?(2017.4)
    C# WinForm中如何让当前应用程序只允许启动一个实例
    C# WinForm 关闭登陆窗体后进程还再内存怎么办?
    ASP.NET在MVC控制器中获取Form表单值的方法
    C#封装CRUD到SqlHelper类解读
    windows 10微软账户不能访问局域网共享,但是本地账户可以访问
    C#高级参数params的使用
  • 原文地址:https://www.cnblogs.com/imwtr/p/5913294.html
Copyright © 2011-2022 走看看