ES6 Generator与异步的同步书写

开始前

咱们历来没有中止过对javascript语言异步调用方式的改造,咱们一直都想用像java那样同步的方式去写异步,尽管Promise可让咱们将异步回调添加到then方法中,可是这种调用方式仍然不那么优雅,es6 中新增长了generator,咱们能够经过他的特性来实现异步任务更加优雅的书写方式。javascript

协程介绍

协程其实和线程,进程是没有关系的,它不是操做系统为咱们提供的api接口,而是经过编程语言或者汇编语言对程序上下文、程序栈来操做实现的。一个线程里面能够包含多个协程,线程的调度是由操做体统来决定的,协程的调度是由用户来决定的。操做系统对其一无所知,由于能够由用户来调度,因此用来执行协做式的任务特别方便。(注意这里是方便,由于能经过协程解决的问题,经过线程和进程也能够解决,可是复杂)java

Generator介绍

Generator 是协程在es6中的实现。它在es6中是一个函数,这个函数能够分阶段执行,也就是说咱们能够在这个函数中的某个位置选择交出当前线程的执行权限,也能够在当前函数外面的某个位置选择将权限再交回这个函数,让它继续执行,这种调度彻底由用户决定。在es6中协程函数是这样的node

function* gen(p) {
    var a = yield p + 1;  //1
    var b = yield p + 2;  //2
    return b;  //3
}

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

经过 var g = gen(1); 仅仅是建立了一个迭代器,函数 gen 里面的内容并无执行函数体的执行时由第一个 g.next(); 开始的 而且将 yield 所在那那条语句执行完后就会返回结果。然后面的语句并无执行。返回值是一个对象,它的第一个属性是 yield 后面表达式的值 (p+1或者p+2的值);第二个属性表示Generator函数是否执行完成。这里咱们经过 yield 执行权限交出去,经过 next 将权限返回。react

function* gen(p) {
    var a = yield p + 1;  //1
    var b = yield a + 1;  //2 注意这里是用到了 a
    return b;
}
var g = gen(1);
g.next();  //{value: 2, done: false}
g.next();  //{value: NaN, done: false} 这里的值是 NaN
g.next();  //{value: undefined, done: true}

g.next();  //{value: 2, done: false}
g.next(2);  //{value: 3, done: false}
g.next(6);  //{value: 6, done: true}

注意这里 //1 处 //2 处 var a = yield p + 1;这条赋值语句中 a 的值并非 p + 1的值。这条语句只是一种写法,这里 a 的值是咱们在第二个 next 中传入的 2 这个很重要 b 的值也是咱们在第三个 next 中传入的 6es6

Generator 的重要特性

由上面的内容咱们总结 3 个关于 Generator 的重要特性编程

1 经过 yield 交出执行权限,经过 next 返回执行权限
2 调用 next 会获得一个返回值,这个值里面包含了 yield 后面的表达式的执行结果
3 咱们能够经过给 next 传递参数,而且能够在 Generator 函数中经过上面所写的特殊方式来引用redux

利用 Generator 的特性来实现异步代码的同步书写

咱们来模拟一个异步函数segmentfault

function post(url, callback) {
    setTimeout(function() {
        var data = { //模拟异步处理结果
            url:url,
            value:10
        };
        callback(data);
    }, 1000);
}

post('http://_ivenj',function(data){
    console.log(data.url);  // http://_ivenj
    console.log(data.value);  //10
});

对应上面的这个异步函数我想经过 Generator 来这样用api

function* gen(url) {
    var data = yield post(url);  //1
    console.log(data.url);
    console.log(data.value);
}
var g = gen('http://_ivenj');
var resultG = g.next();
g.next(resultG.value);

是的,这样写漂亮多了,很像 java 的同步写法。不一样之处就是多了个 yield* ,这个无伤大雅。固然以上这样用确定是不行的。由于 post 毕竟是个异步方法。没有返回值.若是不能实现这样的写法我这半天就是在扯淡,因此经过包装是能够实现的。浏览器

经过如下两点能够实现以上的书写方式

(1)我有一篇文章 react 实践之 redux applyMiddleware方法详解 中介绍了柯里化(Currying)这篇文章虽然是写react的可是柯里化是独立的,这里就要利用柯里化的思想

(2)咱们要在回调中调用 next 来继续执行,(这里有人会想不是不用回调了么,怎么还用,请继续看。。。)

咱们要对 post 的调用形式进行包装

function kPost(url) {
    return function(callback) {
        post(url, callback);
    }
}

经过这个包装,咱们就能保证调用 kPost 就会同步的获得一个返回值

function* gen(url) {
    var data = yield kPost(url);  //1
    console.log(data.url);
    console.log(data.value);
}
//这里执行方式会不一样
var g = gen('http://_ivenj');
//启动任务
var resultG1 = g.next();
var value_resultG1 = resultG1.value; //resultG1.value 必定是一个函数,由于咱们包装了
value_resultG1(function(data){
    g.next(data);  //经过在异步的回调中调用 next 并传递值来确保依赖异步结果的代码能正确执行
});

下面就是总体代码,是上面的片断组合,请你粘贴到浏览器控制台,或者用node运行,就会看到想要的结果

function post(url, callback) {
    setTimeout(function() {
        var data = { //模拟异步处理结果
            url:url,
            value:10
        };
        callback(data);
    }, 1000);
}
function kPost(url) {
    return function(callback) {
        post(url, callback);
    }
}
function* gen(url) {
    var data = yield kPost(url);  //1
    console.log(data.url);
    console.log(data.value);
}
//这里执行方式会不一样
var g = gen('http://_ivenj');
//启动任务
var resultG1 = g.next();
var value_resultG1 = resultG1.value; //resultG1.value 必定是一个函数,由于咱们包装了
value_resultG1(function(data){
    g.next(data);
});

有人会说,怎么不就是将异步回调转移出来了么,还要写回调。这说明你尚未真正体会个中之奥妙。咱们会发现 咱们写的异步

value_resultG1(function(data){
    g.next(data);
});

仅仅是调用了 next 进行告终果的传递,这里面有共同之处,不论是哪种异步,咱们都只传递值。你们的处理都是同样的。真正的业务逻辑确实是用同步的方式写的。那么,咱们能够将共同的地方提取出来,写一个通用的函数去执行这个传值操做,这样,咱们彻底就告别了异步,再也看不到了,好开心。co.js就是一个这种generator的执行库。使用它是咱们只须要将咱们的 gen 传递给它像这样 co(gen) 是的就这样。下面咱们本身写一个 co

Generator执行器

function co(taskDef) {
    //获取迭代器  相似 java 中的外柄迭代子
    var task = taskDef();
    //开始任务
    var result = task.next();
    //调用next的递归函数
    function step() {
        if (!result.done) {  //若是generator没有执行完
            if (typeof result.value === "function") {
                result.value(function(err, data) {
                    if (err) {
                        result = task.throw(err);
                        return;
                    }
                    result = task.next(data);  //向后传递当前异步处理结果
                    step();  //递归执行
                });
            } else {
                result = task.next(result.value);  //若是执行完了就传递值
                step();  //递归执行
            }

        }
    }
    // 启动递归函数
    step();
}

经过 co 执行的完整代码

function post(url, callback) {
    setTimeout(function() {
        var data = { //模拟异步处理结果
            url:url,
            value:10
        };
        callback(data);
    }, 1000);
}
function kPost(url) {
    return function(callback) {
        post(url, callback);
    }
}
function gen(url) {
    return function* () {
        var data = yield kPost(url);  //1
        console.log(data.url);
        console.log(data.value);
    }
}
function co(taskDef) {
    var task = taskDef();
    //开始任务
    var result = task.next();
    // 调用next的递归函数
    function step() {
        if (!result.done) {  //若是generator没有执行完
            if (typeof result.value === "function") {
                result.value(function(err, data) {
                    if (err) {
                        result = task.throw(err);
                        return;
                    }
                    result = task.next(data);  //向后传递当前异步处理结果
                    step();  //递归执行
                });
            } else {
                result = task.next(result.value);  //若是执行完了就传递值
                step();  //递归执行
            }

        }
    }
    // 启动递归函数
    step();
}
    
co(gen('http://_ivenj')); //调用方式就是这么简单

以上代码执行 1s 后会抛出一个异常,而且正确打印{url: "http://_ivenj", value: 10},聪明的你必定知道为何会抛出异常!!!

到这里已经说明白了,而且也说完了,你会想是否是把异步包装成Promise也能够呢,答案是确定的,柯里化的思想只是一种实现方式,Promise 也是一种,你能够本身去琢磨,co.js 就是将两种方式都实现了的一个执行器。es7 中从语言层面对 Generator 进行了包装,在es7 中咱们可使用 asyncawait更优雅的实现相似java的顺序书写方式,asyncawaitGenerator的语法糖,在es7中内置了执行器。别人都说是终极方案。

相关文章
相关标签/搜索