• Home
  • About
    • 谢朝森的个人博客 photo

      谢朝森的个人博客

      该博客主要用来记录个人学习,生活。帮助个人成长以及分享知识的一隅之地。

    • Learn More
    • Twitter
    • Facebook
    • Instagram
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Generator函数的异步调用

传统的异步编程实现方式:

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

ES6中通过Generator函数实现了异步编程

协程的Generator函数实现

Generator函数就是一个封装的异步任务,异步操作需要暂停的地方,都用yield语句注明。

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

Generator函数的数据交换及其错误处理

Generator函数能封装异步任务的主要原因有以下两个

  • 能暂停执行(yield)
  • 能恢复执行(next)

next返回的value,是generator函数向外输出数据,next方法还可以接收参数,向generator函数体内传递数据.

    function* gen(x){
            let y = yield (x + 1);
            return y;
        }
        let g = gen(1);
        g.next(); // {value:3,done:false}
        g.next(1); // {value:1,done:true}

generator函数内部还可以捕捉到该函数外部抛出的错误

    function* gen(x){
        try{
            let y = x + 2; 
        }catch(e){
            console.log(e);  // 出错了
        }
    }
    let g = gen(1);
    g.next(); // {value:3,done:false}
    g.throw('出错了');

Thunk函数

Thunk函数是自动执行Generator函数的一种方式

Thunk函数的含义

编译器的’传名调用’实现,一般情况下是将参数放到一个临时函数中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数。

    function m(x){
        return x + 2;
    }
    m(y + 1);
    // 等同于以下的代码
    let thunk = function(){
        return y + 1;
    }
    function m(thunk){
        return thunk() + 2;
    }

javascript语言中的Thunk函数

javascript是传值调用,它的Thunk函数含义和以上说的Thunk函数是有区别的。在JS中,Thunk函数替换的不是表达式,而是多参数函数, 将其替换成一个只接受回调函数作为参数的单参数函数。

    // 正常版本的readFile
    fs.readFile(filename,callback);
    //Thunk版本的readFile
    let Thunk = function(filename){
        return function(callback){
            return fs.readFile(filename,callback);
        }
    }
    let readFileThunk = Thunk(filename);
    readFileThunk(callback);   // Thunk函数

所有的函数,只要参数有回调函数,就能写成Thunk函数的形式。

    // ES5版本
    var Thunk = function(fn){
        return function(){
            var args = Array.prototype.slice.call(argument);
            return function(callback){
                return fn.apply(this,args);
            }
        }
    }
    // ES6版本
    const Thunk = function(fn){
        return function(...args){
            return function(callback){
                return fn.call(this,...args,callback);
            }
        }
    }

根据上面的转换器,生成如下的读区文件的Thunk函数

    const readFileThunk = function(fs.readFile);
    readFileThunk(filename)(callback);  // Thunk函数

Thunkify模块

Thunkify模块是生产环境的转换器,使用方式如下:

    const thunkify = require('thunkify');
    const fs = require('fs');
    const read = thunkify(fs.readFile);
    read('hello.txt')(callback);

Generator函数的流程管理

使用Thunk函数可以用来做Generator函数的流程管理。具体还需要继续理解…

co模块

co模块是用来Generator函数的自动执行的。

    const gen = function* (){
        let x = yield readFile('afile');
        let y = yield readFile('bfile');
        console.log(x.toString());
        console.log(y.toString());
    }

co模块可以让开发者不要写Generator函数的执行器,只需要将Generator函数作为参数传入co模块中,就会自动执行。

    const co = require('co');
    co(gen);

co函数返回的是一个promise对象,所以可以使用then方法继续执行。

co模块的原理

Generator是一个异步操作的容器,其自动执行需要一种机制,能够在异步操作有了结果的时候交回执行权。 有两种方法能够实现

  • 回调函数,可以包装成为Thunk函数
  • Promise对象。将异步操作封装成为Promise对象,使用then方法交回执行权。 co模块就是将以上两种实现方式封装成为了一个模块,使用co模块的前提是Generator函数中的yield命令后面,只能是Thunk函数或者是Promise对象。

基于Promise的自动执行

基于Promise的自动执行,其实就是不断的调用then方法,传入回调函数。

    const fs = require('fs');
    function readFile(filename){
        return new Promise(function(resolve,reject){
            fs.readFile(filename,function(err,data){
                if(err) return reject(err);
                return resolve(data);
            })
        })
    }
    const gen = function* (){
        let x = yield readFile('Afile');
        let y = yield readFile('Bfile');
    }
    // 手动执行上面的Generator函数
    let g = gen();
    g.next().value.then(function(data){
        g.next(data).value(function(data){
            g.next(data);
        })
    })