几年前,我正在尝试使用NodeJS,发现"Step"库清理了我的一些代码。当希望更新代码时,我注意到Step上有一些“红旗”。 (几年没有更新,只有32次提交等)。
所以我环顾四周,发现Async.js,它具有更多功能和主动维护。
看起来很漂亮和一般。但是我开始尝试应用转换来代替使用它,并且可能没有采取正确的角度。
如果我正确阅读,Step的核心功能似乎是Async.JS将其称为"waterfall" pattern。所以在Step中你会写:
Step(
function firstStepNoArgs() {
foo.asyncCall(this);
},
function secondStep(err, argFromFoo) {
if (err) {
handleError(err);
}
bar.asyncCall(argFromFoo, 1, this.parallel());
baz.asyncCall(argFromFoo, 2, this.parallel());
},
function thirdStep(err, argFromBar, argFromBaz) {
if (err) {
handleError(err);
}
/* etc... */
}
);
如果我不知道更好,我猜你会在async.js中这样做(未经测试,考虑它是伪代码;我说的是理论上的变化我还没有真正追求过还)
function thirdStep(argFromBar, argFromBaz) {
/* etc... */
}
async.waterfall([
function firstStepNoArgs(callback) {
foo.asyncCall(callback);
},
function secondStep(argFromFoo, callback) {
async.parallel([
barResult: function(callback) {
bar.asyncCall(parameterFromFoo, 1, callback);
},
bazResult: function(callback) {
baz.asyncCall(parameterFromFoo, 2, callback);
}
],
function(err, result) {
if (err) {
handleError(err);
} else {
thirdStep(result.barResult, result.bazResult);
}
}
}
],
function(err, result) {
if (err) {
handleError(err);
} else {
/* no-op? just assume third-step runs? */
}
}
);
步骤非常集中和顺序,我的小草案显示它在改编中变得混乱。我错过了什么吗?
所以我的问题是:将明确的Step代码转换为Async.JS的正确方法是什么?或者我选择了错误的库升级到?我不希望我的代码变得更加丑陋,但我也不想依赖于一个似乎有点“死”的库。 : - /
答案 0 :(得分:1)
这里要小心你的回调。回调签名是:
callback(err, arg1, arg2 ...)
因此,如果您的foo.asyncCall
用以下方式调用它:
callback(result1, result2)
然后整个异步将从那一点神秘地失败。正确的成功回调应该以null开头,例如
callback(null, result1, result2)
以下是更正后的代码:
function thirdStep(argFromBar, argFromBaz, callback) {
/* etc... */
callback(null, result);
}
async.waterfall([
function firstStepNoArgs(callback) {
// error callback("failed"). First args is not null means failed
// in case of error, it just goes straight to function(err, result)
foo.asyncCall(callback);
},
function secondStep(argFromFoo, callback) {
// argFromFoo come from previous callback (in this case, result1)
async.parallel([
barResult: function(callback) {
bar.asyncCall(parameterFromFoo, 1, callback);
},
bazResult: function(callback) {
baz.asyncCall(parameterFromFoo, 2, callback);
}
],
function(err, result) {
if (err) {
// in case of error you should do callback(error),
// this callback is from secondStep(argFromFoo, callback).
// this will pass to final function(err, result).
handleError(err);
} else {
// you need to do callback(null) inside thirdStep
// if callback is not called, the waterfall won't complete
thirdStep(result.barResult, result.bazResult, callback);
}
}
}
],
function(err, result) {
if (err) {
handleError(err);
} else {
// everything is executed correctly,
// if any step failed it will gone to err.
}
}
);
答案 1 :(得分:1)
根据要求,实施您正在做的承诺。只需粘贴并运行,您就应该明白这一点。请记住,代码的上半部分是设置模拟功能,以便您可以更好地了解其工作原理。有人可能会告诉我,我应该把它作为一个要点,我也可以这样做。
var Q = require('q');
var foo ={}, bar ={}, baz = {};
// let's mock up some of your objects with some asynch functions
// using setTimeout for async completion
foo.asyncCall = function ( cb) {
setTimeout(function(){ cb(null, 'promises'); },500);
};
bar.asyncCall = function ( arg1, arg2, cb) {
setTimeout(function(){
var result = arg1 + ' can be ' + arg2;
cb(null, result);
},1200);
};
// going to add a will-always-fail function for example purposes
bar.asyncFailure = function (arg1, arg2, cb){
setTimeout(function(){
cb(new Error(arg1 +' offer decent error handling'), null);
},2000); // longer delay - simulate a timeout maybe
};
baz.asyncCall = function ( arg1, arg2, cb) {
setTimeout(function(){
var result = arg1 + ' are really ' + arg2;
cb(null, result);
},800);
};
// set up promise-enbaled calls. Q.denodeify is an easy way to deal with any
// standard node function with a final parameter being an (err,data) callback
// If these are your own functions, you can also create your own promises, but
// Q.nodeify is probably the fastest way to adapt existing code.
bar.promiseFailure = Q.denodeify(bar.asyncFailure);
bar.promiseCall = Q.denodeify(bar.asyncCall);
baz.promiseCall = Q.denodeify(baz.asyncCall);
// this is your wrap up call ('thirdStep' in your code)
function allTogetherNow(arg1, arg2) {
console.log(arg1 +'\n' + arg2);
};
// now we can have some fun
// an example that will run to completion normally
// Q.ninvoke is sort of a 'one-time' denodeify, it invokes a node-style function
// and returns a promise
function example(){
Q.ninvoke(foo,'asyncCall')
.then( function (x) {
return [bar.promiseCall(x, 'confusing at first'),
baz.promiseCall(x, 'awesome after that')]
})
.spread(allTogetherNow)
.fail(function(e){console.log('Had an error')})
.finally(function(){console.log('Calling no matter what from example')});
};
// sometimes things aren't entirely fun though, and there can be an error
function example2(){
Q.ninvoke(foo,'asyncCall')
.then( function (x) {
return [bar.promiseFailure(x, 'confusing at first'),
baz.promiseCall(x, 'awesome after that')]
})
.spread(allTogetherNow)
.fail(function(e){console.log(e)})
.finally(function(){console.log('Calling no matter what from example2')});
};
example();
example2();
对于那些不想打扰它的人,发出的输出是:
promises can be confusing at first
promises are really awesome after that
Calling no matter what from example
[Error: promises offer decent error handling]
Calling no matter what from example2