[学习]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

原文地址:https://www.cnblogs.com/fogwind/p/14171933.html