目录 · 5 个章节
What's wrong with Promise?
Promsie 刚出来的时候欣喜若狂,终于可以摆脱万恶的回调嵌套了。但最近项目中越来越觉得Promise不合场景,当业务逻辑复杂,分支情况变多时代码结构越来越难看。Promise 点then .then的方式注定会依次往下执行, 一同事为了直接返回,使用Promise.reject()强行跳转,感觉又看到了goto的影子.(go die .......).
然后决定试用一波co模块(tj 的co ,bluebird 的coroutine,还有 when/generator这三个模块有一个共同的特点,yield之后的语句需要返回一个Promise. Promise 是为了解决回调操作带来的麻烦的,coroutine从某种意义上讲也是为了解决回调带来的代码可读性问题,那为何还要依赖于Promise呢,TJ在co这个项目里的回答是Promise变得越来越流行,并且官方化了,所以co也加入了这个潮流。私以为还是得从原本的目的出发,几行代码能解决的问题,没必要套个Promise.
基于以上考虑开发了zco 这个模块,除了能和callback无缝使用和必须的feature,还引入了golang里面的defer功能,从获取函数返回值得形式上看也很像golang(这个无心插柳。。)。下面贴出了性能测试,也附上了做同样的事各个coroutine版本的差异。
benchmark
几种书写异步代码方式的性能测试结果,其中co 前缀的是指coroutine版本。 对于coroutine的版本 ,比较了zco ,ES7 async/await ,tj's co, bluebird coroutine, when/generator 五种,其中zco是表现最好的。我也有点意外,为什么es7 的async/await 性能一般。测试代码见 https://github.com/yyrdl/zco .
name timecost(ms) memery(mb)
callback.js 116 30.30078125
async-neo@1.8.2.js 187 48.52734375
promise_bluebird@2.11.0.js 713 93.58984375
co_zco_yyrdl@1.2.0.js 800 76.375
async_caolan@1.5.2.js 1100 122.515625
co_when_generator_cujojs@3.7.8.js 1159 118.69921875
co_when_generator_cujojs_with_bluebird@3.7.8.js 1359 136.29296875
co_tj_with_bluebird_promise@4.6.0.js 1386 125.58203125
promise_native.js 1451 171.72265625
async_await_es7_with_native_promise.js 1526 170.234375
co_when_generator_cujojs_with_native_promise@3.7.8.js 1720 165.0703125
co_tj_with_native_promise@4.6.0.js 1753 162.3203125
async_await_es7_with_bluebird_promise.js 1891 197.7109375
co_coroutine_bluebird@2.11.0.js 4446 244.984375
Platform info:
Windows_NT 10.0.14393 x64
Node.JS 7.7.3
V8 5.5.372.41
Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz × 4
zco
灵感来自于tj的co,创新之处在于不需要Promise了,该模块允许你书写同步代码直接调用异步方法。以返回某一个文件夹下的js 文件列表为例,依次比较zco 版,tj co版,Promise版,纯回调版,es7 async wait五种写法:
zco 版
const zco=require("zco");
const fs=require("fs");
const testDirectory="./cases";
let getAllJsFileZCOVersion=function(dirname){
return zco(function*(next){
let files=[];
let [err,list]=yield fs.readdir(dirname,next);//获得目录下的文件列表
if(err){
throw err;
}
for(var i=0;i<list.length;i++){
let [er,stat]=yield fs.stat(dirname+"/"+list[i],next);//获得文件的信息
if(er){
throw er;
}
if(stat.isFile()&&list[i].endsWith(".js")){//判断是不是js文件
files.push(list[i]);
}
}
return files;
});
}
//then use it
zco(function*(next){
var [err,jsFiles]=yield getAllJsFileZCOVersion(testDirectory)(next);
if(err){
console.log(err.message);
}else{
console.log(jsFiles);
}
})();
//或者这样用
getAllJsFileZCOVersion(testDirectory)((err,jsFiles)=>{
if(err){
console.log(err.message);
}else{
console.log(jsFiles);
}
})
tj co 版
const co=require("co");
const Promise=require("bluebird");
const fs=require("fs");
const testDirectory="./cases";
//需要将原本的回调操作包装成promise的版本
let readdir=function(dirname){
return new Promise((resolve,reject)=>{
fs.readdir(dirname,(err,list)=>{
if(err){
reject(err);
}else{
resolve(list);
}
})
})
}
let stat=function(file){
return new Promise((resolve,reject)=>{
fs.stat(file,(err,stats)=>{
if(err){
reject(err);
}else{
resolve(stats);
}
})
});
}
let getAllJsFileTJCOVersion=function(dirname){
return co(function*(){
let list=yield readdir(dirname);
let files=[];
for(let i=0;i<list.length;i++){
let stats=yield stat(dirname+"/"+list[i]);
if(stats.isFile()&&list[i].endsWith(".js")){
files.push(list[i]);
}
}
return files;
});
}
//then use it
getAllJsFileTJCOVersion(testDirectory).then((files)=>{
console.log(files);
}).catch((err)=>{
console.log(err);
})
Promise 版
const Promise=require("bluebird");
const fs=require("fs");
const testDirectory="./cases";
let readdir=function(dirname){
return new Promise((resolve,reject)=>{
fs.readdir(dirname,(err,list)=>{
if(err){
reject(err);
}else{
resolve(list);
}
})
})
}
let stat=function(file){
return new Promise((resolve,reject)=>{
fs.stat(file,(err,stats)=>{
if(err){
reject(err);
}else{
resolve(stats);
}
})
});
}
let getAllJsFilePurePromiseVersion=function (dirname) {
return readdir(dirname).then((list)=>{
let pros=[];
for(let i=0;i<list.length;i++){
pros.push(stat(dirname+"/"+list[i]));
}
return Promise.all(pros).then((statsList)=>{
let files=[];
for(let i=0;i<statsList.length;i++){
if(statsList[i].isFile()&&list[i].endsWith(".js")){
files.push(list[i]);
}
}
return files;
});
});
}
//then use it
getAllJsFilePurePromiseVersion(testDirectory).then((files)=>{
console.log(files);
}).catch((err)=>{
console.log(err);
})
纯回调版
回调是Node.js 里面最原始的用法,也是性能最好的用法
let getAllJsFilePureCallbackVersion=function(dirname,callback){
let index=0,list=[],files=[];
let alreadyReturn=false;
let _end=function (err) {
if(!alreadyReturn){
alreadyReturn=true;
err?callback(err):callback(undefined,files);
}
}
let checkDone=function () {
if(index===list.length){
_end();
}
}
let jsFile=function () {
for(let i=0;i<list.length;i++){
((j)=>{
fs.stat(dirname+"/"+list[j],(err,stats)=>{
if(err){
_end(err)
}else if(stats.isFile()&&list[j].endsWith(".js")){
files.push(list[j]);
index++;
checkDone();
}
})
})(i)
}
}
fs.readdir(dirname,(err,_list)=>{
if(err){
_end(err);
}else{
list=_list;
jsFile();
}
});
}
//then use it
getAllJsFilePureCallbackVersion(testDirectory,(err,files)=>{
if(err){
console.log(err);
}else{
console.log(files);
}
})
es7 async await 版本
const fs=require("fs");
const testDirectory="./cases";
//async await 需要和Promise一起用,需要将原本的回调包装成Promise
let readdir=function(dirname){
return new Promise((resolve,reject)=>{
fs.readdir(dirname,(err,list)=>{
if(err){
reject(err);
}else{
resolve(list);
}
})
})
}
let stat=function(file){
return new Promise((resolve,reject)=>{
fs.stat(file,(err,stats)=>{
if(err){
reject(err);
}else{
resolve(stats);
}
})
});
}
let getAllJsFileAsyncAwaitES7Version=async function (dirname) {
let list=await readdir(dirname);
let files=[];
for(let i=0;i<list.length;i++){
let stats=await stat(dirname+"/"+list[i]);
if(stats.isFile()&&list[i].endsWith(".js")){
files.push(list[i]);
}
}
return files;
}
getAllJsFileAsyncAwaitES7Version(testDirectory).then((files)=>{
console.log(files);
}).catch((err)=>{
console.log(err);
})
这四种方式,如果考虑性能要求的话个人会选择纯回调的的写法,如果性能要求不那么高,更喜欢 zco的版本,因为更简洁。
根据性能测试结果, zco 是这几个coroutine模块中性能最好的,若追求极限性能的话还是精心设计回调吧,项目地址:https://github.com/yyrdl/zco
目前是稳定版本,亲们都知道的,求star ,客官认为还可以的话就给个呗,当然更欢迎批评意见! ^-^