一: 在Promise出现之前,我们一般通过回调表示异步以及管理并发,但是回调在这两方面有两个主要缺陷:缺乏顺序性和可信任性。
- 如何理解缺乏顺序性
doA(function () {
doB();
doC(function () {
doD();
});
doE();
});
doF();
如果让你第一眼看到这段代码能分析出他的正确顺序吗?(A-F-B-C-E-D) 显然,即使你对这种情况有经验,或者特别熟练,你也需要在代码中不停地上下移动视线。 所以这种缺乏顺序性的编码方式和我们大脑的计划行为是冲突的,我们的顺序阻塞的大脑无法很好地映射到面向回调的异步代码。
- 如何理解缺乏可信任性
//A
ajax('...',function(...){
// C
})
// B
在这段代码汇总,A和B发生在现在,在js主程序的直接控制下。但是C会延迟到未来发生,他其他是在第三方的控制之下,这就是所谓的控制反转,也就说我们将程序中的一部分执行控制权交给某个第三方。
将控制权交给第三方就存在以下多种可能性
- 调用回调过早
- 调用回调过晚(或者没有调用)
- 调用回调的次数太少或太多
- 没有将所需的环境/参数成功传给你的回调函数
- 吞掉可能出现的错误或者异常
- 。。。。。。。还有很多。。。
多数人应该比较认可的一点是,在某种程度上我们应该在内部函数中构建一些防御性的输入参数检查,来减少或阻止无法预料的问题。
function addNums(x,y){
return x+y;
}
addNums(21,21);// 42
addNums('21','21') //2121
// 加入防御性代码
function addNums(x,y){
if (typeof x!=="number" ||typeof y!=="number"){
throw Error('Bad param')
}
return x+y
}
addNums(21,21);// 42
addNums('21','21') //Error:Bad param
对于这样的检查,是很常见的。所以对于异步函数的回调,我们也应该做同样的事情,而不仅仅是针对外部代码。 但是回调并没有为我们提供任何东西来支持这样的检查,我们需要自己构建全部的机制,需要为每个异步回调重复这样的工作,那我们该多累啊。
总结以上分析,我们可以看出回调的问题不仅仅是在缺乏顺序性,bug难跟踪,他最大的问题是控制反转,导致信任链的完全断裂。
二: 那我们如何尝试去挽救回调呢? 其实回调设计存在几个变体,可以解决前面出现的一些信任问题
- 回调分离--一个用于成功通知,一个用于出错通知
function success(data){
console.log(data)
}
function failure(err){
console.log(err)
}
ajax('...',success,failure)
// 在这种设计中failure 函数是可选的,如果没有提供的话,就是假定这个任务可以吞掉
2.error-first风格--回调的第一个参数保留用作错误对象
function response(err,data){
if (err){
console.log(err)
}else {
console.log(data)
}
}
虽然我们尝试去挽救回调,但是存在一下几个问题:
- 并没有解决信任问题,不涉及阻止或者过滤重复回调问题,反而,你可能同时得到成功或失败的结果,或者都没有,你需要编码处理所有情况
- 这个两种弥补措施可复用性不高
所以,我们需要更通用的方式来解决信任问题,不管我们创建多少回调,这一方案都应可以复用,没有重复的代码开销。 最终,es6带着很好的答案登场了--Promise!