ES6 Generator函数异步应用(3)

Generator 函数的异步应用

1.处理异步的传统方法

ES6诞生以前:主要有以下四种方法
  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

2.异步

因为js语言的执行环境是单线程的,如果不是用异步,根本无法使用。
异步就是将一个任务分为两段,第一段执行完毕再去执行其他任务,完了之后在回头处理第二段。
同步就是连续执行,如果程序a没执行玩,b只能等待。

3.回调函数

JavaScript语言对异步编程的实现就是回调函数(callback)也叫做"重新调用"。

举个例子:
    fs.readFile('/data','utf-8', function(err, data){
        if(err){
            throw err
        } else{
            console.log(data);
        }
    })

上面的代码中,readFile函数的第三个参数是回调函数,也就是任务的第二段,等到操作系统返回data文件后
回调函数才执行。
Node约定回调函数第一个参数是err,原因在于第一段执行完时,任务的上下文环境就已经结束了,之后抛出的问题
无法捕捉到,因此只能当做参数被传入第二段。

4.Promise

回调函数本身并没有问题,他的问题在于处理多个函数嵌套上,假定读取A文件之后再读取B文件,代码如下
fs.readFile(fileA,function(err,data){
    fs.readFile(fileB,function(err,data){
        //....
    })
})

这样一次读取以上两个文件,就会出现多重嵌套问题。代码不是纵向发展,而是横向发展,很快就会乱作一团。
多个异步操作形成了强耦合,只要有一个操作需要修改,上下层回调函数都要修改,这种情况就被叫做回调地狱。
因此提出了Promise,他是将回调函数的嵌套更改为链式调用。
{
    readFile(fileA)
    .then(function(data){
        console.log(data)
    })
    .then(function(){
        return readFile(fileB)
    })
    .then(function(data){
        console.log(data)
    })
    .catch(function(err){
        console.log(err)
    })
}
Promise提供then方法加载回调函数,通过catch方法捕捉执行过程中的错误。
但是promise只是最大的问题就是代码冗余,造成了语义不清晰。

5.Generator函数

5.1协程

协程:意思是多个线程互相协作,完成异步应用。
{
    function *asyncJob(){
        //....dosomething
        let f = yield readFile(fileA)
        //...dosomething
    }
}

上面的韩式asyncJob是一个协程,他的奥妙在于其中的yield命令。他表示执行到此处,执行权将交给其他携程
依旧是说,yield命令时异步两个阶段的分界线。
协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续向后执行。他的最大优点是代码的写法非常像同步
操作,如果去除yield命令,几乎一模一样。

5.2协程的Generator函数实现

Generator函数是协程再ES6中的实现,最大的特点就是可以交出函数的执行权。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方都用yield语句注明。
{
    function* gen(x) {
        let y = yield x + 2;
        return y;
    }

    let g = gen(1);
    g.next(); // {value:3,done:false}
    g.next(); // {value:undefined,fone:true}
}

5.3Generator函数的数据交换和错误处理

Generator函数可以暂停执行和恢复执行,这是他能封装异步任务的根本原因。
除此之外还有他的内外的数据交换和错误处理机制。
next的返回值的value属性是Generator函数向外输出数据:next方法和可以接受参数,向Generator函数函数体内输入内容。
{
     function* gen(x) {
        let y = yield x + 2;
        return y;
    }

    let g = gen(1);
    g.next(); // {value:3,done:false}
    g.next(2); // {value:2,fone:true}
}

Generator函数还可以部署错误处理代码,捕获函数体外抛出的错误。
{
    function* gen(x){
        try {
            let y = yield x + 2;
        } catch (e) {
            console.log(e);
        }
        return y;
    }

    let g = gen(1);
    g.next(); 
}

//这意味着代码内部外部的错误可以被函数体内的try...catch语句代码块捕获,这意味着出错的代码与处理错误的代码实现了时间和空间上的分离。

5.4 异步任务的封装

真实使用Generator函数执行一个异步任务
{
    let fetch = require('node-fetch');

    function* gen(){
        let url = 'https:..api.github.com/users/github';
        let result = yield fetch(url);
        console.log(result.bio);
    }

    let g = gen();
    let result = g.next();

    result.value.then(function(data){
        return data.json();
    }).then(function(data){
        g.next(data);
    })
}

//上面的代码首先执行Generator函数获取遍历器对象,使用next方法(第二行)执行异步任务的第一阶段。由于Fetch模块返回的是一个Promise对象,因此要用then方法调用下一个next方法。

6.thunk函数

    thunk函数是自动执行Generator函数的一种方法。

6.1thunk函数的含义

thunk函数属于传名调用,即参数进入函数体,再用到他的时候才求值。

{
    function f(m){
        return m + 2;
    }

    f(5);

    //等同于
    let thunk = function(){
        return x + 5;
    }

    function f(thunk){
        return thunk() + 2;
    }

}

上述代码中,函数f的参数x+5被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。
这就是Thunk函数的定义,他是"传名调用"的一种实现策略。

7.JavaScript 语言的 Thunk 函数

在 JavaScript 语言中,Thunk 函数替换的是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。
{
        // 正常版本的readFile(多参数版本)
        fs.readFile(fileName, callback);
        // Thunk版本的readFile(单参数版本)
        var Thunk = function (fileName) {
            return function (callback) {
                return fs.readFile(fileName, callback);
            };
        };
        var readFileThunk = Thunk(fileName);
        readFileThunk(callback);

        // Generator 函数自动执行,不过不适合异步操作。如果必须保证前一步执行完,才能执行后一步,则下面的自动执行就不可行。
        function* gen() {
            // ...
        }
        var g = gen();
        var res = g.next();
        while (!res.done) {
            console.log(res.value);
            res = g.next();
        }

        // Thunk 函数的自动流程管理
        var fs = require('fs');

        function run(generator) {
            var it = generator(go);

            function go(err, result) {
                if (err) return it.throw(err);
                it.next(result);
            }
            go();
        }
        run(function* (done) {
            var firstFile;
            try {
                var dirFiles = yield fs.readdir('NoNoNoNo', done); // No such dir
                firstFile = dirFiles[0];
            } catch (err) {
                firstFile = null;
            }
            console.log(firstFile);
        });
    }
  • 参考文献 阮一峰著

你可能感兴趣的:(es6,es6)