关于generator异步编程的理解以及如何动手写一个co模块

generator出现以前,想要实现对异步队列中任务的流程控制,大概有这么一下几种方式:javascript

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

第一种方式想必你们是最多见的,其代码组织方式以下:java

function fn(url, callback){
 var httpRequest;    //建立XHR
 httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() :  
    window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : undefined;
  
 httpRequest.onreadystatechange = function(){
  if(httpRequest.readystate === 4 && httpRequest.status === 200){  //状态判断
   callback.call(httpRequest.responseXML); 
  }
 };
 httpRequest.open("GET", url);
 httpRequest.send();
}
 
fn("text.xml", function(){    //调用函数
 console.log(this);   //此语句后输出
});
 
console.log("this will run before the above callback.");  //此语句先输出 

对于一个普通的ajax异步请求来讲,我么在请求开始的时候就要告诉他请求成功以后所要执行的动做,所以就能够相似以这种方式组织代码,控制异步流程。这种调用方式最大的问题就是回调黑洞的问题,一层回调也还好,但涉及到二层、三层、n层的时候就让代码变得复杂很难维护。node

第二种方式本身在前段时间使用backbone.js做为技术栈的项目的开发中深有体会,对于每个ajax请求都对其分配一个自定义事件,在ajax成功返回数据的时候,就会触发自定义的事件完成接下来的动做,控制异步流程,代码以下:jquery

第三种方式和第二种的方式性质上有些相似,若是从发布订阅的角度来看,on方法至关于订阅者/观察者,trigger方法至关于发布者。原理上来讲无非就是维护一个“消息中心”的数组,经过on方法订阅的事件都会推入“消息中心”数组,最后发布的时候将会匹配“消息中心”数组的事件,进而执行相应的流程。
ajax

咱们经过jquery的sub/pub插件完成一个很简单的演示。编程

首先,f2向"信号中心"jQuery订阅"done"信号。数组

 jQuery.subscribe("done", f2);

function f1(){promise

    setTimeout(function () {异步

      // f1的任务代码函数

      jQuery.publish("done");

    }, 1000);

  }

f1();

 jQuery.publish("done")的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引起f2的执行。

第四种方式promise范式,先看一段代码:

咱们只要而且仅须要new一个promise对象,就会发现promise对象的参数函数已经执行了,隔两秒以后输出"执行完成"。

接下来再看一段其实际应用的场景代码:

从本质上来看,Promise是一个构造函数,其自己有all、reject、resolve等方法,同时其原型上有then、catch等方法。经过其用Promise new出来的对象天然就有then、catch方法。而后能够经过then方法中的回调函数,获取到上一段异步操做中返回(经过resolve)的数据。从而实现对异步操做的流程控制。

但个人每一个函数都得被promise对象包装一下,同时一大堆的then...真是一个听蛋疼的事儿...

综上所述对于异步流程的控制,都有其自身的缺陷,咱们最理想的方式即是像操做同步流程那样实现对异步流程的控制,试想一下这样的异步操做流程(加了层层包装,proxy即是发送一个异步请求,接下来的代码即是获取到异步操做返回的数据,细节可暂时忽略):

这感受就是真他妈的舒服,怎么实现这么一个让人很爽的东西呢,因而咱们的主角---伟大的Generator函数登场了。

先理解这么本身悟的一句话:

"javascript是单线程的,顺序执行一段代码,执行到了异步操做,按正常的逻辑走的话就是主队列中的代码继续执行,这时异步队列中的代码还未执行,咱们继续执行的代码也就会发生报错。那么解决问题的关键就是,咱们可以手动控制代码的向下执行,配合一个东西监听到异步操做的已经正常返回了以后,去手动的操做代码的执行流程,这样的话就实现了已同步的方式控制异步代码的执行" 

那么问题变成了解决两个问题。

一、咱们是如何实现对于异步操做是否成功返回的监听。

二、如何手动操做代码的向下执行。

对于第一个问题,咱们采用的方案是使用promise对象的方式,Promise 的编程思想即是,用于“当xx数据准备完毕,then执行xx动做”这样的场景,用在这里再适合不过。

对于第二个问题,咱们即是采用伟大的generator生成器函数,其中的yield特性,可使咱们手动的控制代码的向下执行。

接下来咱们实际的解决一个问题:实现对于读取文件异步操做的控制,当读取完文件以后打印读取的内容。

咱们依赖于node环境,首先经过promise对其进行封装,实现数据成功的监听。咱们手下代码以下:

var fs = require('fs');
var readFile = function(fileName) {
    return new Promise(function(resolve,reject) {
        fs.readFile(fileName, function(err, data) {
            if (err) return reject(err);
            resolve(data);
        })
    })
} 

有了这个东西,咱们即可以经过其then()表达式,"当数据加载完后,执行某个动做"。那咱们执行的动做是啥,天然就是执行下一步的代码的操做。继续看代码:

var gen = function* () {
    var f1 = yield readFile('/Users/dongzhiqiang/Desktop/demo.txt');
    var f2 = yield readFile('/Users/dongzhiqiang/Desktop/demo.txt');

    console.log('<<<<<<<<<<<<<<<<<<<<<<<',f1.toString());
    console.log('>>>>>>>>>>>>>>>>>>>>>>>>',f2.toString());
}

这个就是一个generator函数的表达式,在这个函数里面,遇到generator就会执行相似于return的操做。咱们经过next()即可以实现手动的控制代码的向下执行。

那么咱们如何控制代码的执行流程呢,看下面一段:

var g = gen();

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

这段的具体解释就是,咱们经过promise封装的对象实现了对于异步操做数据返回的监听,当数据返回的时候,咱们就经过next()执行下一步的操做,同时把上步操做的值带入到下一个阶段的执行流程之中。

可是上面这段操做非常蛋疼啊,咱们要的是一个能通用的操做流程函数。那么咱们继续对这段循环操做进行封装:

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

  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

run(gen);

因而一个很是简单的co模块便诞生了。
最终代码以下:

咱们把函数放到run的执行器里面,便实现了同步操做异步代码的过程。 

未完待续...

相关文章
相关标签/搜索