zoukankan      html  css  js  c++  java
  • [学习]generator函数

    使用generator函数改造回调函数

    写在前面的话:

    generator函数总是与yield一起使用的。

    基础知识(ES6)

    generator函数的声明:

    1. 可以使用构造函数 GeneratorFunction 生成
    2. 使用function* expression 定义

    使用第二种更方便。

    声明一个generator函数:

    function *test() {
        // 要执行的代码
    }
    

    调用generator函数并不会立即执行函数中的语句,而是返回一个Generator对象。

    Generator对象有三个方法:

    • Generator.prototype.next() 返回一个由 yield表达式生成的值,值的结构 {value: '值', done: '状态'}, value的值是yield关键字右侧语句的计算值,done表示generator函数是否最终执行完成。
    • Generator.prototype.return() 返回给定的值并结束生成器。
    • Generator.prototype.throw() 向生成器抛出一个错误。

    如果要执行generator函数中的语句还需要调用next()函数。

    调用next()函数后会在遇到的第一个yield语句处暂停,如果要继续执行后面的语句需要继续调用next()函数,如果遇到yield语句会再次暂停,直到所有yield语句执行完毕,返回函数最终的值。

    function *test() {
        console.log(1);
        yield 'qqq';
        console.log(2);
        yield 'ppp';
        return 'ooo';
    }
    let testGene = test(); // 没有任何打印
    console.log(testGene.next()); // 打印1, next()函数返回{value: 'qqq', done: false}
    console.log(testGene.next());  // 打印2, next()函数的返回值{value: 'ppp', done: false}
    console.log(testGene.next());  // next()的返回值{value: 'ooo', done: true}
    

    注意:yield语句本身并不会返回值(或者说总是返回undefined),真正返回值的是next()函数。

    看下面的例子:

    function *test() {
        let x = yield 'xxx';
        console.log(x);
    }
    let testGene = test();
    console.log(testGene.next()); // {value: 'xxx', done: false}
    console.log(testGene.next()); //打印x的值为undefined,next()的返回值 {value: undefined, done: true}
    

    对于上面的代码还有要说明的一点,当调用完第一个next()函数后,let x = yield 'xxx';这条语句仅仅执行了等号右面的部分,等号左面的部分要等到下次调用next()时才执行。这就是前面所说的【遇到yield语句就暂停的意思】。

    可以给next()函数传入参数,用来赋值。

    function *test() {
        let x = yield 'xxx';
        console.log(x);
    }
    let testGene = test();
    console.log(testGene.next()); // {value: 'xxx', done: false},在这一步给next()函数传入参数并不会赋值给x。
    console.log(testGene.next(123)); //打印x的值为123,next()的返回值 {value: undefined, done: true}
    

    可以稍微花两分钟好好感受一下上面的代码。

    有了上面这些基础知识就可以对回调函数进行改造了,如果想了解其他的关于generator函数的基础知识可以点这里MDN

    改造回调函数

    假如有两个接口,调用第一个接口成功反馈结果后再调用第二个接口。使用回调函数的写法,需要在调用第一个接口的回调函数里再调第二个接口。
    下面使用generator函数来实现同步的顺序调用。

    使用知乎的接口:

    先写一个ajax请求函数:

    let http = require('https');
    function requestAjax(url,callback) {
        http.get(url, (res) => {
            let rawData = '';
            res.on('data', (chunk) => { rawData += chunk; });
            res.on('end', () => {
                try {
                    const parsedData = JSON.parse(rawData);
                    console.log(parsedData);
                    callback(parsedData);
                } catch (e) {
                    console.error(e.message);
                    callback(e);
                }
            });
        })
    }
    

    再写一个generator函数,这个函数的大致结构如下:

    function *syncAjax(callback) {
        let firstRes = yield requestAjax(url,callback);
        console.log('firstRes', firstRes);
        if(firstRes.data) {
            let secondRes = yield requestAjax(url,callback);
            console.log('secondRes', secondRes);
        }
        return {firstRes,secondRes};
    }
    

    我们知道generator函数需要手动调用next()函数才能把函数里的语句执行完,所以我们需要在请求结果的回调里调用next()函数才能让generator函数执行下去。

    写一个回调函数

    function requestHandler(res) {
        geneInstance.next(res); // geneInstance是调用syncAjax()返回的Generator对象
    }
    

    为了让requestHandler函数能访问到geneInstance,需要用到闭包,也就是说还需要启动函数:

    function run(generatorFnc) {
        let geneInstance = generatorFnc(requestHandler);
        geneInstance.next();
        function requestHandler(res) {
            geneInstance.next(res); // geneInstance是调用syncAjax()返回的Generator对象
        } 
    }
    

    代码已经写完了,下面是汇总在一起的:

    let http = require('https');
    function requestAjax(url,callback) {
        http.get(url, (res) => {
            let rawData = '';
            res.on('data', (chunk) => { rawData += chunk; });
            res.on('end', () => {
                try {
                    const parsedData = JSON.parse(rawData);
                    callback(parsedData);
                } catch (e) {
                    callback(e);
                }
            });
        })
    }
    
    function *syncAjax(callback) {
        let firstRes = yield requestAjax('https://www.zhihu.com/api/v4/search/top_search/tabs/hot/items',callback);
        let secondRes = null;
        console.log('firstRes======', firstRes);
        if(firstRes.data) {
            secondRes = yield requestAjax('https://www.zhihu.com/api/v4/hot_recommendation',callback);
            console.log('secondRes======', secondRes);
        }
        return {firstRes,secondRes};
    }
    
    function run(generatorFnc) {
        let geneInstance = generatorFnc(requestHandler);
        geneInstance.next();
        function requestHandler(res) {
            geneInstance.next(res); // geneInstance是调用syncAjax()返回的Generator对象
        } 
    }
    
    run(syncAjax);
    

    运行上面的代码,在控制台按顺序打印出了接口数据,是不是很妙?

    参考资料

    [1] MDN

    [2] 深入理解js中的yield

    [3] 用ES6 Generator替代回调函数

    扩展阅读

    [1] 如何使用koa2+es6/7打造高质量Restful API

    [2] 如何优雅的在 koa 中处理错误

    [3] Koa2 还有多久取代 Express

  • 相关阅读:
    《Exceptional C++ Style中文版》 作者:Herb Sutter 定价39元
    11.24 《阿猫阿狗2》精美包装艳丽登场
    STLport 5.0.1 available for download.
    编程时需要注意的一些原则
    面向对象设计原则
    ASP.NET下MVC设计模式的实现
    string 与stringbuilder的区别
    工厂方法模式(Factory Method)
    面向对象设计(OOD)的基本原则
    HUTXXXX DNAANDDNA 贪心
  • 原文地址:https://www.cnblogs.com/fogwind/p/14171933.html
Copyright © 2011-2022 走看看