一个能让js性能飙到嗨的流程库
发布于 8 年前 作者 892280082 6247 次浏览 来自 分享

yqthen.js

**写这个流程库的目的不是为了解决异步回调,而是为了最大限度的挖掘js异步模型的优势。将js的长处发挥到极致 **

async/await确实能写出这个好看的代码,但是我这是流程库啊。。。。 我比较和参考的对象是 async或者thenjs 才是啊。 而且我这个库很适合用来写脚本,你总不能写脚本的时候把方法单独拉出来,封装一遍吧。

1.async常用的API在这里都可以进行链式调用,并且API更加友好。 2.这个库在并发处理上有长处,会让写出的程序有一定的性能提升。

例如下面的例子中 你需要打开数据库和读取文件,耗时都为100MS; 如果用下面的promise形式的话,需要200MS才能完成。generator和await也是200MS;

then(()=>{ db.open() }).then(()=>{ fs.readFile() })

如果用这个库的go方法的话 可以并发完成 只需要100MS,go链里的方法都是并发运行,当go链运行结束后会继续运行then链。

then.go(()=>{ db.open() }).go(()=>{ fs.readFile() }).then...

这样简单的处理,程序的性能就拉开了100MS,而且代码也变得更加美观。

测试用例

业务要求 要求:并发读取文件和打开数据库连接。存入数据库都是并发。 读取一个helloJs.text文件,并将内容保存到’DBXXX’数据库中, 在将该数据提交给远程服务器,地址http:XXX。 如果提交成功,在把文件转成行,每一行一条记录,存到DBXXX数据库中。

伪代码: 一般java或php代码的写法

try{
 file = readFile('helloJs.text'); //读取文件
 db = DB.open('DBXXX'); //打开数据库
 db.save(file); //数据库保存数据
 request('http:xxx',file); //像服务器提交数据
 lines = file.convertLine;//转换成行
 for(var i=0;i<lines.length;i++){
  	db.save(lines[i]); //每行一条数据,保存到数据库
 }
}catch(e){
	echo e  //捕获异常
}   

代码貌似没有问题,但是这里读取文件和打开数据库的操作是可以同时运行的, 如果java用实现的话,可能就要使用多线程模型了。

如果用js实现的话,我想大家都会。 但是用这个库写出来的代码绝对是最简洁优美的。 这里的读取文件和打开数据库是并发的。 yqthen

var then = require('yqthen');

var file,db;
then
.go((next)=>{
	readFile('helloJs.text',(err,file)=>{ //读取文件
		file = file;
		next(err);
	})
})
.go((next)=>{
	DB.open('DBXXX',(err,db)=>{  //打开数据库
		db = db;
		next(err);
	})
})
.then((next)=>{
	db.save(file,(err)=>{  //数据库保存数据
		next(err);
	})
}).then((next)=>{
	request('http:xxx',file,(err)=>{ //像服务器提交数据
		next(err);
	})
})
.each(file.convertLine,(next,line)=>{
	db.save(line,(err)=>{  //每行一条记录保存到数据库
		next(err);
	})
})
.then((next)=>{
	callback(null); //操作成功
})
.fail((next,err)=>{
	callback(err);  //捕获异常
})

这里在特地比较一下async

async.waterfall([
	function(callback){
		async.parallel([ //并行运行
			function(){ //读取文件
				readFile('helloJs.text',(err,file)=>{ //读取文件
					file = file;
				})
			},
			function(){ //打开数据库
				DB.open('DBXXX',(err,db)=>{  //打开数据库
					db = db;
				})
			}
		],
		function(err, results){
			callback(null, fs, db,callback);
		});
	},
	function(fs, db, callback){
		request('http:xxx',file,(err)=>{ //像服务器提交数据
			callback(null,fs,db);
		})
	},
	function(fs,db, callback){
		async.eachSeries(fs.convertLine, function iteratee(line, callback) { //循环并发
			db.save(line,(err)=>{  //每行一条记录保存到数据库
				callback(err);
			})
		});
	}
], function (err, result) {
	console.log(err,result);  
});
//我已经被这个嵌套搞崩溃了!!!

传送门: yqthen

虽然用了yqthen框架,但是异步代码还是没有同步代码简洁,这也是没有办法的事,希望js能像 形式同步更近一点。

但是我这里读取文件和打开数据库用go链连接,也就说这两个方法是并发的,是同时运行的。只有当 这两步操作成功后,才会像数据库保存数据,最后在向服务器提交数据。

  • 我很非常喜欢node也热爱开源,如果大家觉得还行,请给我一个星星 鼓励一下吧。
26 回复

自己顶一个,喜欢各位大牛能给点意见。嘿嘿~

star先

来自酷炫的 CNodeMD

用不明白bluebird吧?

没benchmark,说提升性能你们也信。。。

@i5ting 其实我想表达的不是算法的性能提升,目的只是分享下处理异步的经验。可能我的表达不够清楚,我更新了下文章内容,你可以再看一下。在多给点意见,嘿嘿。

鼓励一下,楼主精神可嘉,把benchmark放上来,我就star,嘿嘿~

@DevinXian 大帅哥,你就star 一下吧。 你放心,bm我会做的.

@892280082 我并没有看出什么特别的地方啊,bb都能实现,而且更好,为什么要造这个轮子呢?

@i5ting 谢谢你还是看了啊。我这个是流程库,如果跟async比的话还是有些创新的。例如我把async的 series,eachSeries,waterfall都链起来写了,API更加友好。至bb 应该属于Promise,不是一个东西吧。如果可以 你能简写下bb的伪代码吗?

我觉得再美也美不过async/await吧,如下:

(async function () {
  let file = await readFile('helloJs.text'); //读取文件
  let db =  await DB.open('DBXXX'); //打开数据库
  await db.save(file); //数据库保存数据
  await request('http:xxx',file); //像服务器提交数据
  lines = file.convertLine;//转换成行
  for(var i=0;i<lines.length;i++){
  	await db.save(lines[i]); //每行一条数据,保存到数据库
  }
})( );

至于性能,我觉得都是用的nodejs的nonblocking,应该都差不多吧。

@ron-liu 你们为什么一定要拿流程库跟这些比啊。。。不说封装的代码量吧。
let file = await readFile(‘helloJs.text’); //读取文件 let db = await DB.open(‘DBXXX’); //打开数据库 ** 业务要求是并发 读取文件和数据库 ** await db.save(lines[i]); 这里要求的也是并发。 这些 async-wait是不容易做到的吧。

@ron-liu 感谢评论啊

@892280082 我觉得用什么都无所谓,关键看是否解决了问题,你的问题主要是异步代码同步化,至于并发,nodejs本来就是non blocking的,promise也好,callback也好,yield/generator也好,async/await也好都是并发的。

async/await 通过babel的transform,已经是production ready了。

@ron-liu 嗯 是的 用什么工具都看个人喜好。 只是我有一点不明白 let file = await readFile(‘helloJs.text’); //读取文件 let db = await DB.open(‘DBXXX’); //打开数据库 async-await的话 不是有顺序的吗 先读取文件 在打开数据库吗? yield/generator 也只是循环调用,也是有先后顺序的吧,需要前面的任务完成在进行下一个任务,也没有并发的效果啊。 假如 读取文件和打开数据库都需要100MS 那不论是async-await还是yield/generator 完成任务都需要200MS吧 如果并发的话应该只用100MS,这就是我写这个库的目的,用最简单的写法,发挥js最大的性能。 我不是钻牛角尖,我只是想搞清楚,是不是我以前理解错了。嘿嘿。

non blocking 只是无锁吧,并发还是需要代码逻辑实现的吧。

如果你要顺序执行,那就: let file = await readFile(‘helloJs.text’); //读取文件 let db = await DB.open(‘DBXXX’); //打开数据库 如果你要它并行执行,那就: await Promise.all([ readFile(’…’), DB.open(‘DBXXX’)]);

并不是说并行就没有顺序执行。

@ron-liu Promise.all 好像一个出错就会退出 其他Prmoise 就不能执行了 有解吗?

抱歉打扰一下大家的讨论移动互联网B2B公司-海致微办公求前端大牛有兴趣的亲请随时联系我,手机/微信-13381088126,MeggieYang~~

@elrrrrrrr 据我知道,promise一旦发出,就没有办法收回,也就是说一个失败,其它的应该是照常运行。见下图,有图有真相! 如果要同时成功或者同时rollback,那就是事务的问题了。 untitled4.png

@892280082 毕竟Promise与async/await都已或者即将是标准,对于你说的并发

let promises = [ 
		readFile(‘helloJs.text’), // 这里假设这两个方法均返回 Promise<T>
		DB.open(‘DBXXX’)
	],
let [file, db] = await Promise.all(promises);

这样子也就Ok了,效果很好的

@elrrrrrrr 一个简单的方式每个err都catch,然后返回resolve的promise

let promise = [p1,p2,p3].map(p => p.catch(err => Promise.resolve('Err but ok.')));
Promise.all(promise);

@WangZishi 厉害啊,受教了。 不过感觉,这个上手难度不小啊。

@892280082 嗯,不过毕竟是原生的代码。未来的通用性,可移植性都是可期待的。哦,对了!还有v8的神优化加持。

@ron-liu 虽然执行,但是不会有返回值了… @WangZishi : D 这个写法不错,可以统一处理异常了…

@elrrrrrrr 明白你的意思了,确实不能在then获取部分成功的resolved值了。 一个不太好的方法是,执行的时候就赋值给一个全局变量了。

回到顶部