generator 出现之前,想要实现对异步队列中任务的流程控制,大概有这么一下几种方式:
第一种方式想必大家是最常见的,其代码组织方式如下:
- 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 层的时候就让代码变得复杂很难维护。
第二种方式自己在前段时间使用 backbone.js 作为技术栈的项目的开发中深有体会,对于每一个 ajax 请求都对其分配一个自定义事件,在 ajax 成功返回数据的时候,就会触发自定义的事件完成接下来的动作,控制异步流程,代码如下:
第三种方式和第二种的方式性质上有些类似,如果从发布订阅的角度来看,on 方法相当于订阅者 / 观察者,trigger 方法相当于发布者。原理上来说无非就是维护一个 "消息中心" 的数组,通过 on 方法订阅的事件都会推入 "消息中心" 数组,最后发布的时候将会匹配 "消息中心" 数组的事件,进而执行相应的流程。
我们通过 jquery 的 sub/pub 插件完成一个很简单的演示。
首先,f2 向 "信号中心"jQuery 订阅 "done" 信号。
- jQuery.subscribe("done", f2);
function f1(){
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 是单线程的,顺序执行一段代码,执行到了异步操作,按正常的逻辑走的话就是主队列中的代码继续执行,这时异步队列中的代码还未执行,我们继续执行的代码也就会发生报错。那么解决问题的关键就是,我们能够手动控制代码的向下执行,配合一个东西监听到异步操作的已经正常返回了之后,去手动的操作代码的执行流程,这样的话就实现了已同步的方式控制异步代码的执行"
那么问题变成了解决两个问题。
1、我们是如何实现对于异步操作是否成功返回的监听。
2、如何手动操作代码的向下执行。
对于第一个问题,我们采用的方案是使用 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() 表达式," 当数据加载完后,执行某个动作 "。那我们执行的动作是啥,自然就是执行下一步的代码的操作。继续看代码:
- vargen =function* () {
- varf1 = yield readFile('/Users/dongzhiqiang/Desktop/demo.txt');
- varf2 = yield readFile('/Users/dongzhiqiang/Desktop/demo.txt');
- console.log('<<<<<<<<<<<<<<<<<<<<<<<',f1.toString());
- console.log('>>>>>>>>>>>>>>>>>>>>>>>>',f2.toString());
- }
这个就是一个 generator 函数的表达式,在这个函数里面,遇到 generator 就会执行类似于 return 的操作。我们通过 next() 便可以实现手动的控制代码的向下执行。
那么我们如何控制代码的执行流程呢,看下面一段:
- varg = gen();
- g.next().value.then(function(data){
- g.next(data).value.then(function(data){
- g.next(data);
- });
- });
这段的具体解释就是,我们通过 promise 封装的对象实现了对于异步操作数据返回的监听,当数据返回的时候,我们就通过 next() 执行下一步的操作,同时把上步操作的值带入到下一个阶段的执行流程之中。
但是上面这段操作很是蛋疼啊,我们要的是一个能通用的操作流程函数。那么我们继续对这段循环操作进行封装:
- function run(gen){
- varg = gen();
- function next(data){
- varresult = g.next(data);
- if(result.done)return result.value;
- result.value.then(function(data){
- next(data);
- });
- }
- next();
- }
- run(gen);
于是一个非常简单的 co 模块便诞生了。最终代码如下:
我们把函数放到 run 的执行器里面,便实现了同步操作异步代码的过程。
未完待续...
来源: http://www.cnblogs.com/qqqiangqiang/p/6667117.html