ES6 异步编程之三:Generator续

前言

《ES6 异步编程之一:Generator》中实现了一个异步函数调用链,它是一个顺序调用链,很类似责任链模式,但现实往往不是平铺直叙的,更多的其实是峰回路转,本文将继续讨论更多Generator的用法。

作为函数的Generator

在之前的示例中,我们更多的是把生成器作为一个迭代器来循环调用每个函数,但忽视了生成器也是个函数,意味着生成器内部可以实现复杂的业务逻辑,以下的代码通过yield等待文件读取结果并对结果进行特定的处理,


    function* generator() {
        let r1 = yield get('a');
        if (r1) {
            let r2 = yield get('b');
            if (r2) {
                console.log(yield get('d'));
            }
        } else {
            console.log(yield get('c'));
        }
    }
    
    let g = generator();
    g.next();

如果get是个异步调用,以上的代码想要能够执行则需要get函数执行得到结果后调用生成器的next方法来推进,这要求get函数能持有生成器对象,这显然并不容易。

偏函数

偏函数(Partial Function)是对函数定义域的子集定义的函数,形式上就是指定任意部分参数生成一个新的函数,如下:

    function sum(a, b, c) {
        return a + b + c;
    }
    
    function sum1(a) {
        return function(b, c) {
            return a + b + c;
        };
    }
    
    function sum2(a, b) {
        return function(c) {
            return a + b + c;
        };
    }
    
    sum(1, 2, 3) == sum1(1)(2, 3); //true
    sum(1, 2, 3) == sum2(1, 2)(3); //true

一般在设计异步调用api时,我们总是声明一个参数来接收回调函数,当和偏函数相结合就变成这样:

    function get(f, callback) {
        delay(100, function(s) {
            callback(s + ':get ' + f);
        });
    }
    
    get('a', func);  //调用get时必须立即传入一个函数
    
    //转换成偏函数形式:
    function partialGet(f) {
        return function(callback) {
            delay(100, function(s) {
                callback(s + ':get ' + f);
            });
        };
    }
    
    let pGet = partialGet('a');   //可以先生成一个函数
    pGet(func);  //需要时再传入回调函数执行

从上面的例子中可以发现,偏函数能使定义和执行分离,说来巧了,生成器可用于定义业务逻辑而生成器的next用于推进业务执行,二者也是相互分离的。

生成器和偏函数

基于前面这么多铺垫,假设get就是一个偏函数,如下:

    function get(f) {
        return function(callback) {
            delay(100, function(s) {
                callback(s + ':get ' + f);
            });
        };
    }

这意味着,yield get('a')使得next函数执行的结果其value属性值是个函数,该函数的参数是一个能接收get异步结果的回调函数,即:

    g.next().value(function(value) {
        g.next(value); //value成为yield的返回并继续推进业务逻辑
    });

通过递归可以不断的执行生成器的next方法,一个全新的通过生成器来实现业务逻辑的run方法便呼之欲出了,


function run(gen) {
    let g = gen();

    function next(lastValue) {
        let result = g.next(lastValue); //将上一个异步执行结果传出给当前的yield并执行下一个yield
        if (result.done) {
            return result.value;
        }
        //value是偏函数返回的新函数,它的参数是个用来接收异步结果的回调函数
        result.value(next);  //next作为接收异步结果的回调函数
    }

    next();
}

run(generator);

综合以上例子不难发现另外一个好处,通过偏函数可以使异步调用api不受生成器的侵入,《ES6 异步编程之一:Generator》中实现的异步调用需要将生成器作为参数,有兴趣的话你可以尝试改造一下之前的示例。

现在通过新编写的run函数就可以来执行本文一开始编写的那个生成器了。

thunkify

偏函数的方案对api和生成器也是有侵入的,他要求:

  1. api必须是偏函数形式;

  2. 生成器定义业务逻辑,每个yield 后面的函数必须是调用偏函数;

第二个问题是本方案的核心机制所要求,但第一个问题我们可以通过thunkify来解决,api依旧按照get(value, callback)方式定义,但定义生成器时需要将异步api调用通过thunkify转换为偏函数,如下:

    let Thunkify = require('thunkify');
    
    let thunkifiedGet = Thunkify(get);
    
    function get(f, callback) {
        delay(100, function(s) {
            callback(s + ':get ' + f);
        });
    
    }
    
    function* generator() {
        let r1 = yield thunkifiedGet('a');
        if (r1) {
            let r2 = yield thunkifiedGet('b');
            if (r2) {
                console.log(yield thunkifiedGet('d'));
            }
        } else {
            console.log(yield thunkifiedGet('c'));
        }
    }
    原文作者:码农彭盛
    原文地址: https://segmentfault.com/a/1190000008326347
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞