我们都知道在Promise等异步技术还没出现时,回调函数是实现异步的重中之重。很多人知道使用回调函数,却不知回调函数出现的作用主要是实现异步。但随着现在应用越来越复杂强大,回调函数在异步处理中已然是不够用了,尽管还是很多人坚持认为回调函数够用。
本博客主要谈谈回调函数的两个缺陷:
看下面伪代码(嵌套回调):
listen("click",function handler(evt){
setTimeout(function request(){
ajax("http://url1",function response(text){
if(text=="hello"){
handler();
}else if(text=="world"){
request();
}
})
},500)
})
你很可能很熟悉这样的代码。这里我们得到了三个函数嵌套在一起构成的链,其中每个函数代表异步序列中的一个步骤。
这段代码常常被称为回调地狱,有时也被称为毁灭金字塔。
分析:
一眼望去,这段代码很自然地将其异步性映射到了顺序大脑中。
首先(现在)我们有:
listen("..",function hander(...){
//...
});
接着就是将来,我们有:
setTimeout (function request(...){
//...
},500)
接着还是将来,我们有:
ajax("..",function response(...){
//...
});
最后(最晚的将来),我们有:
if(...){
//...
}
else...
我们的大脑就是按这种方式线性追踪代码的。
似乎这样分析,回调函数实现异步也不缺乏顺序是吗?这只是一个偶然,实际的异步程序代码更加杂乱。在大脑的演习中,我们需要熟练的对执行顺序排序,从一个函数跳到下一个函数。对于这样满是回调的代码,理解异步流也不是不可能,但肯定很吃力,也不容易。
我们再来看下面这个伪代码:
doA(function(){
doB();
doC(function(){
doD();
})
doE();
})
doF();
看到这样的代码,你能一眼在大脑中呈现执行顺序吗?我觉得这还是需要费一番劲的。
它们的执行顺序是:
有可能有人觉得这是嵌套回调才这么复杂而已,可以使用链式回调的嘛
看下面代码 (链式回调):
listen("click",hander);
function hander(){
setTimeout(request,500);
}
function request(){
ajax("http://url1",response);
}
function response(text){
if(...){
hander();
}else if(...){
request();
}
}
虽然链式回调比嵌套回调好一点,但它还是跟回调地狱一样,假设你写的代码顺序不像执行顺序一样,还是得上上下下得查看。更何况上面得代码还是最优情况,真实得异步JS代码要混乱得多。
利用回调函数写的异步,无论是在写得过程搞得头晕,以后得维护工作更让人头大。
看下面伪代码:
//A
ajax("..",function(..){//第三方库封装的api
//C
});
//B
//A 和 //B发生于现在,在JS得主程序直接控制之下。而//C会延迟到将来发生,并且是在第三方控制下---- 在本例中就是函数ajax(…)。从根本来说,这种控制的转移通常不会给程序带来很多问题。但也不是不会出现问题,因为在第三方提供的工具,不是你自己写的代码,这就不再你的直接控制之下。我们称这为“控制反转”,也就是把自己程序的一部分执行控制交给某个第三方。在你的代码又没有一份与第三方工具的明确契约。
利用个故事来说明情况:
假设你是个开发人员,为某个销售昂贵电视的网站建立商务结账系统。你已经做好了结账系统的各个方面,但在最后一页,当用户点击了“确定支付”购买电视时,你就需要调用第三方函数来跟踪这个交易。第三方提供了下面函数:
analytics.trackPurchase(purchaseData,fun)
这就说明你需要传入一个回调函数,在函数中,你须提供向客户收费和展示感谢页面的最终代码。
analytics.trackPurchase(purchaseData,function(){
chargeCreditCard();
displayThankyouPage();
})
看着很简单,已经部署完成,通过测试,一切正常,进行产品部署。过了一段时间,突然收到客户的投诉,客户购买了一台电视机,但是信用卡却被刷了五次。然后通过分析日志,你得出了一个结论:你使用的第三方工具出于某种原因把你的回调调用了五次而不是一次。第三方工具的文档也没说明会出现这种情况。最后只能联系第三方来处理这个问题。
这里可能出现错误的情况:
这就是一个麻烦列表,这时,你可能已经慢慢认识到,对于被传给你无法信任的工具的每个对调模拟都将不得不创建大量的混乱逻辑。这就是回调函数缺乏可信任性。