看了everyauth源码,第一次看解决异步流程问题的实现方法,感到很惊讶,为了更好地学习,实现了这个流程的简化版。
例子
先看看这段代码,功能是从本地文件读到一个url—请求这个url—把结果写入另一个文件。
var fs = require('fs'), http = require('http'); fs.readFile('./url.txt', 'utf8', function (err,data) { http.get(data, function(res){ var body = ''; res.on('data', function(c){ body += c; }).on('end', function(){ fs.writeFile('./fetchResult', data + body, function(e) { if (e) console.log('error', e); else console.log('done'); }); }); }).on('error', function(e){ console.log(e); }); });
这段代码包括了三个步骤三个功能,但耦合在一起,可读性差,难以修改,对任意一部分修改或增加都要看完整坨代码才能找到,即时把每个callback都抽成一个变量,这一整个流程也是无法分离的。
改进版
对这种情况,everyauth使用了一种方法,可以把整个流程的实现代码写成这样:
engine .do('fetchHtml') .step('readUrl') .accepts('') .promises('url') .step('getHtml') .accepts('url') .promises('html') .step('saveHtml') .accepts('url html') .promises(null) .readUrl(function(){ //read url from file ... }) .getHtml(function(url){ //send http request ... }) .saveHtml(function(url, html){ //save to file ... }) .start('fetchHtml')
do是一串流水方法的开始,step指定每一个步骤对应的方法名,promises表示此步骤返回的变量名,accepts表示此步骤接受的参数(由前面step的方法提供的变量)。接下来是链式地实现每一个step的方法。
整个过程很清晰,程序的自我描述很好,把一段异步的流程按同步的方式写出来了。若要修改其中某个步骤,直接定位到某个步骤对应的方法就行,无需把整个流程的代码通读。若要增加步骤,也只需要在那些step流程上插入新的step然后实现具体方法,可以获取前面step提供的任何参数。
how it works
实现它用到四个对象:promise/step/sequence/engine
promise是基础,相信很多人熟悉它的概念,简单来说就是把多个callback放到一个promise对象里,在适当的地方通过这个对象调用这些callback。在这里的作用是:当step执行结束时,通知队列执行下一个step。具体地说就是把下一个step的函数保存到前一个step的promise里,前一个step完成任务时,带着数据回调下一个step进入执行。
step负责执行一个步骤,传入对应参数,并把执行结果(return值)按指定的promises名保存起来,以供下一个step使用。
sequence管理step链,让注册的step可以一步步往下执行。
engine是提供对外接口的对象,管理保存每一个do请求里的step和sequence,通过configurable配置自身的可动态添加的方法。