├── .gitignore ├── README.md ├── index.js ├── package.json └── test ├── test.js ├── test2.js ├── testDone.js ├── testEach.js └── testGo.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/* 2 | /node_modules/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | yqthen.js 2 | ==== 3 | ###API已经稳定,本人已经用于项目中。 4 | ###如果出现bug,请npm update yqthen 升级到最新版。1.50 5 | ###这个demo是这个库比较好的实践,最大化发挥了nodejs的性能。 6 | 7 | [demo演示](https://github.com/892280082/yqthen_fs_eample) 8 | 9 | #usage 10 | ```bin 11 | npm install yqthen 12 | ``` 13 | 14 | ###习惯async的同学不妨试一试这个库,让你的代码更加清爽。 15 | * async 常用的API在这里都可以进行链式调用,并且API更加友好。 16 | * go 简单暴力的实现多任务并发。 17 | * each 异步循环实现的非常优美,并且可以限制并发数。 18 | * 参数迭代和异常捕捉都比async更加方便。 19 | 20 | ## 下面的用例分别用 yqthen 和 async实现。 21 | 22 | 1. 读取一个helloJs.text文件,并将内容保存到’DBXXX’数据库中, 23 | 2. 在将该数据提交给远程服务器,地址http:XXX。 24 | 3. 如果提交成功,在把文件转成行,每一行一条记录,存到DBXXX数据库中。 25 | 26 | ```js 27 | var then = require('yqthen'); 28 | 29 | var file,db; 30 | then 31 | .go((next)=>{ 32 | readFile('helloJs.text',(err,file)=>{ //读取文件 33 | file = file; 34 | next(err); 35 | }) 36 | }) 37 | .go((next)=>{ 38 | DB.open('DBXXX',(err,db)=>{ //打开数据库 39 | db = db; 40 | next(err); 41 | }) 42 | }) 43 | .then((next)=>{ 44 | db.save(file,(err)=>{ //数据库保存数据 45 | next(err); 46 | }) 47 | }) 48 | .then((next)=>{ 49 | request('http:xxx',file,(err)=>{ //像服务器提交数据 50 | next(err,file.convertLine); 51 | }) 52 | }) 53 | .each((next,line)=>{ 54 | db.save(line,(err)=>{ //每行一条记录保存到数据库 55 | next(err); 56 | }) 57 | },20) //最大并发20 58 | .then((next)=>{ 59 | callback(null); //操作成功 60 | }) 61 | .fail((next,err)=>{ 62 | callback(err); //捕获异常 63 | }) 64 | ``` 65 | 66 | ###这里在特地比较一下async 67 | ```js 68 | async.waterfall([ 69 | function(callback){ 70 | async.parallel([ //并行运行 71 | function(){ //读取文件 72 | readFile('helloJs.text',(err,file)=>{ //读取文件 73 | file = file; 74 | }) 75 | }, 76 | function(){ //打开数据库 77 | DB.open('DBXXX',(err,db)=>{ //打开数据库 78 | db = db; 79 | }) 80 | } 81 | ], 82 | function(err, results){ 83 | callback(null, fs, db,callback); 84 | }); 85 | }, 86 | function(fs, db, callback){ 87 | request('http:xxx',file,(err)=>{ //像服务器提交数据 88 | callback(null,fs,db); 89 | }) 90 | }, 91 | function(fs,db, callback){ 92 | async.eachSeries(fs.convertLine, function iteratee(line, callback) { //循环并发 93 | db.save(line,(err)=>{ //每行一条记录保存到数据库 94 | callback(err); 95 | }) 96 | }); 97 | } 98 | ], function (err, result) { 99 | console.log(err,result); 100 | }); 101 | //我已经被这个嵌套搞崩溃了!!! 102 | ``` 103 | 104 | ##API 105 | 1. then(Function) -待运行函数 106 | 2. each(Array?,Function,Number?) -Array不设置会自动获取next传递的第二个参数 107 | 3. go(Function) -一个并发任务链,一个then链里面可以有多个go链。 108 | 4. fail(Function) -捕获异常 109 | 5. done(err,args) -结束方法 110 | 111 | 112 | ###each+done方法示例 113 | ```js 114 | var ids=[_id1,_id2.....];//数据库取出来的ID数组 115 | var results = [];//查询的结果数组 116 | then.each(ids,(next,value,index)=>{ 117 | /** 118 | *一般查询数据库都会限制并发数,超过数据库最大连接数的并发是没有意义的。 119 | *并且同一时刻像消息队列放入过多的函数,很容易导致栈溢出。 120 | *这是用async的朋友经常遇到的问题 121 | */ 122 | DB.findOne(value,(err,doc)=>{ 123 | results.push(doc); 124 | next(err); 125 | }) 126 | },20).done((err)=>{ 127 | if(err){ 128 | console.log("运行出错":,err); 129 | }else{ 130 | console.log("查询结果",results); 131 | } 132 | }) 133 | ``` 134 | 135 | 136 | QQ 892280082 逐梦 137 | PS: 名字非主流,但是好多年不想换了,各位客官忍忍。 138 | 139 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Debug = function (){ 2 | var args = Array.prototype.slice.call(arguments); 3 | args.unshift('yqthen Error:'); 4 | console.log.apply(console,args); 5 | }; 6 | 7 | function slice (args, start) { // 将arguments 转成数组 8 | start = start || 0; 9 | if (start >= args.length) return []; 10 | var len = args.length; 11 | var ret = Array(len - start); 12 | while (len-- > start) ret[len - start] = args[len]; 13 | return ret; 14 | } 15 | 16 | function ThenEntity(){ 17 | this._arrayFuncs = []; 18 | this._errorFuncs = []; 19 | this._funcProgress = []; 20 | this._arrayIndex = 0; 21 | this._errorIndex = 0; 22 | this._lastArgs = {}; 23 | this._doneFunc = null; 24 | 25 | this._init = function(func){ 26 | func = func || function(next){next()}; 27 | 28 | var _this = this; 29 | _this._arrayFuncs.push(func); 30 | 31 | setTimeout(function(){ 32 | _this._arrayFuncs[_this._arrayIndex++](_this._defer); 33 | },0); 34 | 35 | }.bind(this); 36 | 37 | this._defer = function(err){ 38 | 39 | var args = slice(arguments); 40 | err = args.shift(); //去掉第一个占位的err 41 | 42 | if(args.length > 0){ //如果有参数传递,保存这些参数 43 | this._lastArgs = args; 44 | }else if(this._lastArgs.length>0){//如果没有参数,使用之前保存的参数 45 | this._lastArgs.shift(); 46 | args = this._lastArgs; 47 | } 48 | 49 | var nextFunc = this._arrayFuncs[this._arrayIndex]; 50 | 51 | if(nextFunc === 'done'){ 52 | if(!this._doneFunc) 53 | return; 54 | args.unshift(err); 55 | var _this = this._doneFunc.apply(null,args); 56 | this._doneFunc = null; 57 | return _this; 58 | } 59 | 60 | if(!err){ 61 | nextFunc = this._arrayFuncs[this._arrayIndex++]; 62 | }else{ 63 | args.unshift(err); 64 | this._arrayIndex = this._funcProgress.shift();//获取下次运行的方法的下表 65 | nextFunc = this._errorFuncs[this._errorIndex++]; 66 | } 67 | 68 | if(nextFunc){ 69 | args.unshift(this._defer); 70 | nextFunc.apply(null,args); 71 | } 72 | 73 | }.bind(this); 74 | 75 | this.then=function(func){ 76 | 77 | this._arrayFuncs.push(func); 78 | return this; 79 | 80 | }.bind(this); 81 | 82 | this.fail=function(func){ 83 | 84 | this._funcProgress.push(this._arrayFuncs.length); 85 | this._errorFuncs.push(func); 86 | return this; 87 | 88 | }.bind(this); 89 | 90 | this.each=function(array,func,limit){ 91 | 92 | var thenEachPojo ; 93 | if(array instanceof Array){ 94 | limit = limit || 0; 95 | thenEachPojo = new ThenEachEntity(array,func,this,limit); 96 | }else{ 97 | limit = func || 0; 98 | func = array; 99 | array = null; 100 | thenEachPojo = new ThenEachEntity(array,func,this,limit); 101 | } 102 | return this.then(thenEachPojo.start); 103 | 104 | }.bind(this); 105 | 106 | this.go = function(func){ 107 | 108 | var thenGo = new ThenGoEntity(func,this); 109 | this.then(thenGo.start); 110 | return thenGo; 111 | 112 | }.bind(this); 113 | 114 | this.done = function(func){ 115 | this._doneFunc = func; 116 | return this.then('done'); 117 | }.bind(this); 118 | 119 | } 120 | 121 | function ThenGoEntity (func,parent){ 122 | this._arrayFuncs = [func]; 123 | this._nextCount = 0; 124 | this.parent = parent; 125 | this.then = parent.then; 126 | this.each = parent.each; 127 | this.fail = parent.fail; 128 | this.done = parent.done; 129 | 130 | this._next = function(err){ 131 | if(err) 132 | return this.parent._defer(err); 133 | 134 | if(++this._nextCount === this._arrayFuncs.length) 135 | return this.parent._defer(); 136 | 137 | }.bind(this); 138 | 139 | this.start = function(){ 140 | 141 | var _this = this; 142 | this._arrayFuncs.forEach(function(tempFunc){ 143 | tempFunc(_this._next); 144 | }); 145 | 146 | }.bind(this); 147 | 148 | this.go = function(func){ 149 | this._arrayFuncs.push(func); 150 | return this; 151 | }.bind(this); 152 | 153 | } 154 | 155 | 156 | /** 157 | */ 158 | function ThenEachEntity (array,itretor,parent,limit){ 159 | this._array = array; 160 | this._itretor = itretor; 161 | this._nextCount = 0; 162 | this.parent = parent; 163 | this.fail = parent.fail; 164 | this.done = parent.done; 165 | this.go = parent.go; 166 | this._limit = limit; 167 | 168 | this._selfNext = function(err){ 169 | 170 | if(err) 171 | return this.parent._defer(err); 172 | 173 | if(++this._nextCount >= this._array.length) 174 | return this.parent._defer(); 175 | 176 | if(this._limit){ 177 | var nextIndex = this._nextCount+this._limit -1; 178 | if(nextIndex < this._array.length) 179 | this._itretor(this._selfNext,this._array[nextIndex],nextIndex); 180 | } 181 | 182 | }.bind(this); 183 | 184 | this.start = function(){ 185 | 186 | if(!this._array) 187 | this._array = this.parent._lastArgs[1]; 188 | 189 | if(this._array.length === 0) 190 | return this.parent._defer(); 191 | 192 | 193 | if(this._limit >= this._array.length) 194 | this._limit = 0; 195 | 196 | var _this = this; 197 | 198 | if(this._limit){ 199 | 200 | for(var i=0;i{ 7 | _.delay(()=>{ 8 | callback(_.random(0,20)>19? '网络打开错误' : null); 9 | },100); 10 | } 11 | } 12 | 13 | var DB = { 14 | open:(callback)=>{ 15 | _.delay(()=>{ 16 | callback(_.random(0,20)>19? '数据库打开错误' : null); 17 | },100); 18 | } 19 | } 20 | 21 | 22 | var Customer = { 23 | find:(callback)=>{ 24 | _.delay(()=>{ 25 | var docs = [{_id:"abc1",name:"小镇"}, 26 | {_id:"abc2",name:"小丽"}, 27 | {_id:"abc3",name:"小芳"}, 28 | {_id:"abc4",name:"小海"}]; 29 | callback(_.random(0,20)>19?'查询数据库出错':null,docs); 30 | },50); 31 | } 32 | }; 33 | 34 | var WeiXinAuth2 = { 35 | getOpenIdForId:(_id,callback)=>{ 36 | _.delay(()=>{ 37 | return callback(_.random(0,20)>19?'第三方请求出错':null,_id+"adsf234gfh"); 38 | },100); 39 | } 40 | }; 41 | 42 | /** 43 | 测试流程如下: 44 | 1.Net.openSocket -打开网络socket链接,耗时 100MS 5%几率抛出异常 45 | 46 | 2.DB.open -打开数据库链接 耗时100MS 5%几率抛出异常 47 | 48 | 3.Customer.find -查询数据库 获得文档数量 4条 耗时100MS 5%几率抛出异常 49 | 50 | 4.WeiXinAuth2.getOpenIdForId -向微信服务器请求,获得每个用户的openId 每条耗时50MS 5%几率抛出异常 51 | 52 | ##################################### 53 | 同步方法处理业务 54 | 打开NET 100MS 55 | 打开数据库 100MS 56 | 查询数据100MS 57 | 查询每个用户openId = 50*4 58 | 共耗时 500MS 59 | 60 | ###################################### 61 | yqThen 流程库异步处理业务 62 | db和net为并行打开,共耗时100MS 63 | 数据库查询100MS 64 | 微信服务器并发请求 50MS 65 | 共耗时 250MS 66 | 67 | yqThen 流程库特点: 68 | 1.多任务并行执行 + promise + each异步循环 均为链式回调。 69 | 2.统一的报错处理 70 | 3.统一的参数迭代。 例如查询数据库的next(err,docs)的docs参数 迭代到之后的each,then方法中。 71 | */ 72 | then 73 | .go((next)=>{ //并行打开socket链接 74 | Net.openSocket((err)=>{ 75 | next(err); 76 | }); 77 | }) 78 | .go((next)=>{ //并行打开数据库 79 | DB.open((err)=>{ 80 | next(err); 81 | }); 82 | }) 83 | .then((next)=>{ //查询数据库 84 | Customer.find((err,docs)=>{ 85 | next(err,docs); 86 | }); 87 | }) 88 | .each((next,value)=>{ //并发请求微信服务器获得openId 89 | WeiXinAuth2.getOpenIdForId(value._id,(err,openId)=>{ 90 | value.openId = openId; 91 | next(err); 92 | }); 93 | }) 94 | .then((next,docs)=>{ //处理结果 95 | console.log("result:",docs); 96 | }) 97 | .fail((next,err)=>{ //捕获异常 98 | console.log(err); 99 | }); 100 | 101 | 102 | -------------------------------------------------------------------------------- /test/test2.js: -------------------------------------------------------------------------------- 1 | /** 2 | 业务要求 3 | 读取一个helloJs.text文件,并将内容保存到'DBXXX'数据库中, 4 | 在将该数据提交给远程服务器,地址http:XXX。 5 | 6 | 伪代码: 7 | 一般java或php代码的写法 8 | try{ 9 | 10 | file = readFile('helloJs.text'); //读取文件 11 | db = DB.open('DBXXX'); //打开数据库 12 | db.save(file); //数据库保存数据 13 | request('http:xxx',file); //像服务器提交数据 14 | 15 | }catch(e){ 16 | echo e //捕获异常 17 | } 18 | 19 | 代码貌似没有问题,但是这里读取文件和打开数据库的操作是可以同时运行的, 20 | 如果java用实现的话,可能就要使用多线程模型了。 21 | 22 | 如果用js实现的话,我想大家都会。但是用这个库写出来的代码绝对是最简洁优美的。 23 | 24 | var then = require('yqthen'); 25 | 26 | var file,db; 27 | then.go((next)=>{ 28 | readFile('helloJs.text',(err,file)=>{ //读取文件 29 | file = file; 30 | next(err); 31 | }) 32 | }).go((next)=>{ 33 | DB.open('DBXXX',(err,db)=>{ //打开数据库 34 | db = db; 35 | next(err); 36 | }) 37 | }).then((next)=>{ 38 | db.save(file,(err)=>{ //数据库保存数据 39 | next(err); 40 | }) 41 | }).then((next)=>{ 42 | request('http:xxx',file,(err)=>{ //像服务器提交数据 43 | callback(err); 44 | }) 45 | }).fail((next,err)=>{ 46 | callback(err); //捕获异常 47 | }) 48 | 49 | 50 | 虽然用了yqthen框架,但是异步代码还是没有同步代码简洁,这也是没有办法的事,希望js能像 51 | 形式同步更近一点。 52 | 53 | 但是我这里读取文件和打开数据库用go链连接,也就说这两个方法是并发的,是同时运行的。只有当 54 | 这两步操作成功后,才会像数据库保存数据,最后在向服务器提交数据。 55 | 56 | 57 | */ -------------------------------------------------------------------------------- /test/testDone.js: -------------------------------------------------------------------------------- 1 | var then = require("../index"); 2 | var _ = require("underscore"); 3 | 4 | var array = _.range(2,100); 5 | 6 | then.each(array,(next,value,index)=>{ 7 | setTimeout(()=>{ 8 | console.log('value',value,index); 9 | next(); 10 | },100); 11 | },3).done((err)=>{ 12 | console.log('test OVER'); 13 | console.log("array",array.length); 14 | 15 | }); 16 | 17 | then.each([],function(next,value){ 18 | console.log(value); 19 | next(); 20 | }).done(function(err){ 21 | console.log('[] test over!'); 22 | }); -------------------------------------------------------------------------------- /test/testEach.js: -------------------------------------------------------------------------------- 1 | var then = require("../index"); 2 | var _ = require("underscore"); 3 | 4 | then((next)=>{ 5 | 6 | setTimeout(()=>{ 7 | console.log('test start!'); 8 | next(null,_.range(1,100)); 9 | },0); 10 | 11 | }).each((next,value,index)=>{ 12 | 13 | setTimeout(()=>{ 14 | console.log('value:',value,index); 15 | next(); 16 | },20); 17 | 18 | },3).done((err)=>{ 19 | err = err || '运行结束'; 20 | console.log('err:',err); 21 | }); 22 | 23 | 24 | then((next)=>{ 25 | console.log('test2 start!'); 26 | next(); 27 | }).each([1,2,3],(next,value)=>{ 28 | console.log(value); 29 | next(); 30 | }).each(['a','b','c'],(next,value)=>{ 31 | console.log(value); 32 | next(); 33 | }).done((err)=>{ 34 | console.log('test2 OVER!',err); 35 | }) -------------------------------------------------------------------------------- /test/testGo.js: -------------------------------------------------------------------------------- 1 | var then = require("../index"); 2 | var _ = require("underscore"); 3 | 4 | 5 | then((next)=>{ 6 | 7 | console.log('testGo1 start'); 8 | next(); 9 | 10 | }) 11 | .go((next)=>{ 12 | 13 | setTimeout(()=>{ 14 | console.log('1func 100ms'); 15 | next(); 16 | },100); 17 | 18 | }) 19 | .go((next)=>{ 20 | 21 | setTimeout(()=>{ 22 | console.log('2func 200ms'); 23 | next(); 24 | },200); 25 | 26 | }) 27 | .go((next)=>{ 28 | setTimeout(()=>{ 29 | console.log('3func 300ms'); 30 | next(); 31 | },300); 32 | }) 33 | .then((next)=>{ 34 | setTimeout(()=>{ 35 | console.log('then1'); 36 | next(); 37 | },0); 38 | }) 39 | .go((next)=>{ 40 | 41 | setTimeout(()=>{ 42 | console.log('4func 100ms'); 43 | next(); 44 | },100); 45 | 46 | }) 47 | .go((next)=>{ 48 | setTimeout(()=>{ 49 | console.log('5func 100ms'); 50 | next(); 51 | },100); 52 | }) 53 | .then((next)=>{ 54 | setTimeout(()=>{ 55 | console.log('then2'); 56 | next(); 57 | 58 | },0); 59 | }).done((err)=>{ 60 | console.log('testGo1 OVER!',err); 61 | }) --------------------------------------------------------------------------------