先来看这段异步回调的例子
//A
setTimeout(function() {
//C
}, 1000);
//B
其中 A B表示程序的前半部分(也就是现在),而C则表示程序的后半部分(也就是将来部分)
A,B先执行,等待1s后执行C部分
换句话说,回调函数包裹或者说封装了程序的(continuation)
我们的思考方式是一步一步的,但是从同步转换到异步之后,可用的工具(回调)却不是按照一步一步的方式来表达的
我们的顺序阻塞式的大脑计划行为无法很好的映射到面向回调的异步代码,对于他们在代码中表达异步的方式,我们的大脑需要努力才能跟得上(例如多层嵌套的回调)
再来看一个把回调当做continuation(也就是后半部分)的例子
//A
$.ajax({url: "..",success: function() {
//C
}});
//B
//A //B发生于现在,在JavaScript主程序的直接控制下
而//C会延迟到将来发生,并且是在第三方的控制下(本例中是函数ajax(..))
我们把这称为控制反转,也就是把自己程序的一部分交给某个第三方
而在你的代码和第三方工具(一组你希望有人维护的东西)之间并没有明确表达的契约
那么,你可能会遇到下面这些问题
回调设计存在一些变体,试图解决上面提到的部分信任问题
为了更优雅的处理错误,有些API设计提供了分离回调(一个用于成功通知,一个用于出错通知)
$.ajax({
url: "..",
success: function() {
},
error: function() {
}
});
这种设计下,API的出错处理函数常常是可选的,如果没有提供的话,就是假定这个错误可以吞掉(ES6中的promise就是使用了这种分离回调设计)
还有一种常见的回调模式叫做"error-first风格"(有时也称为"Node风格",因为几乎所有的Node.js API都采用这种风格),其中回调的第一个参数保留用作错误对象(如果有的话)
如果成功的话,这个参数就会被清空/置假(后续的参数就是成功数据)
如果产生了错误结果,那么第一个参数就会被置起/置真(通常就不会传递其他结果)
首先,这两种风格并没有解决主要的信任问题,比如重复调用回调的问题
而且这种模式可复用性不高,这意味着我们得一遍又一遍的给每个回调添加这样的代码