JavaScript回调

顺序与异步

回调是编写和处理 JavaScript 程序异步逻辑的最常用 方式。确实,回调是这门语言中最基础的异步模式

所以,如果说同步的大脑计划能够很好地映射到同步代码语句,那么我们的大脑在规划异 步代码方面又是怎样的呢?
答案是代码(通过回调)表达异步的方式并不能很好地映射到同步的大脑计划行为。

嵌套回调与链式回调

一种典型的回调地狱:

1
2
3
4
5
6
7
8
9
listen( "click", function handler(evt){
setTimeout( function request(){
ajax( "http://some.url.1", function response(text){
if (text == "hello") {
handler(); }
else if (text == "world") {
request();
} } );
}, 500) ; } );

复杂的回调,对于程序的调试和追踪代理来很高的复杂度。

这就是回调方式最主要的缺陷:对于它们在代码中表达异步的方式,我们的大脑 需要努力才能同步得上。

控制反转:

也就是把自己程序一部分的执行控制交给某 个第三方。在你的代码和第三方工具(一组你希望有人维护的东西)之间有一份并没有明 确表达的契约。

利用代码的执行依赖于,ajax(..)返回的结果。

省点回调

为了更优雅地处理错误,有些 API 设计提供了分离回调

1
2
3
4
5
6
7
function success(data) {
console.log( data );
}
function failure(err) {
console.error( err );
}
ajax( "http://some.url.1", success, failure );

S6 Promise API使用的就是这种分离回调设计。

还有一种常见的回调模式叫作“error-first 风格”(node风格),第一个参数保留用作错误对象,如果成功,这个参数会被清空或者置假(后面的参数就是成功数据)。

1
2
3
4
5
6
7
8
9
function response(err,data) { // 出错?
if (err) {
console.error( err );
}
// 否则认为成功 else {
console.log( data );
}
}
ajax( "http://some.url.1", response );

小结

第一,大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,但是回调表达异步流 程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大。难于理解的代码 是坏代码,会导致坏 bug。

第二,也是更重要的一点,回调会受到控制反转的影响,因为回调暗中把控制权交给第三 方(通常是不受你控制的第三方工具!)来调用你代码中的 continuation。这种控制转移导 致一系列麻烦的信任问题,比如回调被调用的次数是否会超出预期。