用co玩转异步

以前我在关于Promise的文章中提到了co这个库。在这篇文章里,我将写一写本身对它的认识。git

Trust me,用了co库,你不想用别的,来它半斤异步调用你一口能吃仨。github

可是我对Tj大神的co库源码谈不上深刻理解。因此,若有乱讲,欢迎指正。segmentfault

我这里默认读者对PromiseGenerator有必定的认识。数组

先安利本身写的两篇关于Promise的文章:浏览器

下面我就来谈谈co这个牛逼的库。async

ES7 async/await

干吗,咱们不是讲ES6么,怎么跳到ES7了?函数

由于co要作的事情,就是ES7的async/await要作的事情。
也就是说,这种解决异步的思路,已经在ECMA标准的考虑之中了。未来咱们浏览器的JS引擎就能够原生实现这件事而不是经过JavaScript代码模拟。要知道,引擎的实现和代码的实现那是彻底两码事。ui

一点题外话

多一句嘴:有些同窗混淆了ECMA标准、引擎支持和代码实现的联系。

这里引用老赵在知乎里面回答问题时说的一句话:

ES7是个标准,定义的是what to do不是how to do,为何好多人仍是搞不清这二者的区别。

ECMAScript定义了一些JavaScript语言层面要作的事情,这是一个标准。之因此要制定这个标准,是为了防止浏览器各自为政而出现JS引擎对同一行代码的解释出现不一样的状况。

也就是说,ECMA制定标准,咱们就能够按照这个标准来写JavaScript代码。写好的JavaScript代码由浏览器的JS引擎来解释,最终变成计算机能读懂的代码来执行。


async/await

上代码:

var foo = function(){

    return new Promise(resolve => {
        // 异步操做以后
        resolve('OK');
    });
}

async funtion bar(){
    
    var result = await foo();
    
    console.log(result); 
    
}

bar(); // ==> 打印'OK'

咱们注意到,这段代码用了两个新的关键字asyncawait。并且有两件神奇的事情发生了:

  1. bar函数中包含了一个返回Promise对象的语句,并且Promise中存在异步代码。可是这条语句接下来的语句明显是等待Promise对象中的代码异步执行完毕以后才执行的。(不然不会获得异步以后的值)

  2. Promise对象resolve的值,并无在then中进行处理,而是直接做为返回值返回到Promise对象外面了.

这就是async/await的魔法。在函数前面加上async关键字以后,内部的代码会识别await关键字。此时假设await后面的语句返回一个Promise对象,那么执行的代码将会等待,直到Promise对象变为resolve状态。而且Promise对象中resolve的值将直接做为await语句的返回值返回。而后再执行await语句以后的语句。

今后咱们就能够无痛的撸异步代码,妈妈不再用担忧回调金字塔的出现和异步流程逻辑搞不定的状况了!

另外一个奇妙的事情就是,率先支持这一特性的浏览器竟然是微软的Edge。大概是由于C#语言早就出现async/await,而且TypeScript也支持这一特性的缘故吧。

co

咱们但愿全部的浏览器都及早支持这一特性。可是值得欣喜的一点就是,虽然V8尚未支持,Tj大神早就利用Generator的方式实现了一个ES6版本的async/await!(膜拜脸)

co函数形式

一样是上面的逻辑,咱们用co实现一次:

// 首先咱们须要将co引入,假设咱们使用commonJS的方式  

const co = require('co');

var foo = function(){

    return new Promise(resolve => {
        // 异步操做以后
        resolve('OK');
    });
}

co(function* (){
    
    var result = yield foo();
    
    console.log(result); 
    
}); // ==> 打印'OK'

咱们看到,co函数接收一个Generator生成器函数做为参数。执行co函数的时候,生成器函数内部的逻辑像async函数调用时同样被执行。不一样之处只是这里的await变成了yield

简单版本的co代码

要实现以上的逻辑,结合Generator的特性,co函数应该:

  • 在函数体内将Generator生成器函数执行并生成生成器实例(在此命名为gen),而后经过gen.next方法的调用,不断执行生成器函数内部的代码。

  • 执行next方法以后,返回的Promise在生成器函数执行环境以外执行,并取出resolve值,做为返回值做为next方法的参数返回到Generator执行环境中。

基于以上两点,咱们能够大致实现一个简化版的co,代码以下:

const co = function(genFunc){  
    const gen = genFunc(); // 获得生成器实例  
    
    const deal = (val) => {
        
        const res = gen.next(val); 
        
        // 这里处理了异步逻辑,
        // 在回调中去递归,不断执行next
        // 这样就将resolve的值传回了Generator
        res.value.then(result => deal(result));
        
    }
    
    deal(); // 第一次触发递归
}

去掉括号等等,只有短短六行代码。

more

原理性的东西大约就是这样了。可是co作的不止这些。

  1. 以前coyield后的语句并不支持Promise对象,而是一个特殊的函数,叫作thunk。目前co两者都支持。
    此处我并不打算重复性解释thunk版本,由于原理性的东西实现起来是差很少的。

  2. co函数是有返回值的,也是一个Promise对象。

    • 当生成器函数内的逻辑执行完毕且没有错误以后,这个Promise对象(co返回值)变为resolve状态,且将生成器的返回值做为resolve出来的值。

    • 若生成器函数内返回一个Promise对象,那么co函数返回值就是这个Promise对象。

    • 若生成器函数抛出了错误,那么这个错误做为reject出来的值,将Promise对象的状态变为reject

    这样咱们就能够将错误放进其返回值的.catch方法中统一处理。

  3. 在生成器函数内部,咱们也可使用try...catch语句获取错误对象。

  4. 生成器的yield后面能够跟一个元素值为Promise对象的数组,这个数组内Promise对象内的异步逻辑将并发执行,并返回一个数组。(相似于Promise.all方法)

  5. 假设生成器执行以前须要从外部传入参数,co库提供了一个方法:

var fn = co.wrap(function* (val) {
  
     return yield Promise.resolve(val);

  });

  fn(true).then(function (val) {

  });

结束

以上是一点微小的看法。谢谢指正。

相关文章
相关标签/搜索