如果说Google的V8引擎是Node.js的心脏,那么回调则是Node的脉络,回调能够激活跨模块和应用程序之间平衡的、非堵塞的异步控制流程,因此,在实际编写时,我们需要一个通用的可依赖的回调编程方式,error-first回调,也称为errorback或errback或node-style callback,它们都是用来解决这个问题。
node这种高度依赖回调数据的编程风格要早于Javascript语言本身,Continuation-Passing Style (CPS)是对今天Node.js这种回调用法的古老学术用语,在CPS中, 一个"持续函数"(称为 回调)是被作为参数传入,然后再被其他代码调用运行,这就允许不同函数跨整个应用滞后或提前地进行异步处理控制。
Node.js依赖异步代码保存快速性能,这样也就依赖回调模式,回调使用得太多会陷入嵌套回调,回调里嵌套回调,变得非常复杂,程序代码难于阅读,整个应用的流程迷失在嵌套回调中,error-first模式导入解决这个问题。
error-first回调有两条定义规则:
- 1.回调函数的第一个参数保留给一个错误error对象,如果有错误发生,错误将通过第一个参数err返回。
- 2.回调函数的第二个参数为成功响应的数据保留,如果没有错误发生,err将被设置为null, 成功的数据将从第二个参数返回。
下面以fs.readFile()为例子说明:
fs.readFile('/foo.txt',function(err,data){
// TODO: Error Handling Still Needed!
console.log(data);
});
fs.readFile()从一个文件路径读取文件,一旦完成调用你的回调函数,如果一切正常 ,文件内容将被在data参数中返回,但是如果有错误发生,比如文件不存在或权限不允许,第一个err参数将会包含出错的信息。
至于错误处理这是由开发人员自己处理。当一个函数传递错误给回调函数,它就不管这些错误是如何处理,readFile函数不知道读取文件会出现怎样的错误,但是它可以预期,否则就会有灾难后果,但是取代自己处理错误,readFile()会传播错误回到你的原始提交处理者。
当你遵循这种一致的模式时,错误能够如你所愿被一层层传播,每个回调能选择忽略或处理,或基于错误信息再传播。
if(err) {
// Handle "Not Found" by responding with a custom error page
if(err.fileNotFound) {
return this.sendErrorMessage('File Does not Exist');
}
// Ignore "No Permission" errors, this controller knows that we don't care
// Propagate all other errors (Express will catch them)
if(!err.noPermission) {
return next(err);
}
}
当有这种坚固的回调协议在心中,你就再也不会局限于一次只能使用一个回调,回调能够在队列或并行被调用或串行化方式,如果你要读取10种不同的文件,或进行100个不同的API调用,再也没有理由说不可以一次性进行了。
async库是一种高级回调应用包,因为有error-first回调模式,它就很容易被使用:
// Example taken from caolan/async README
async.parallel({one:function(callback){
setTimeout(function(){
callback(null,1);
},200);
},
two:function(callback){
setTimeout(function(){
callback(null,2);
},100);
}},
function(err,results){
// results is equal to: {one: 1, two: 2}});
当然其他还有Promise等方式。