之前写过文章,介绍promise的一些基本用法,但对promise没有更深层次的理解,接下来希望我能更清楚的介绍它。
我之后几乎所有的内容都是《你不知道的javaScript 中卷》中的内容
我所理解的promise
我们都知道promise解决了回调地狱的问题,我一直认为是解决了回调可读性查,嵌套问题,不易维护
例如
btn.addEventListener('click', function () {
setTimeout(function request() {
ajax("http://some.url.1", function response(text) {
if (text == "hello") {
handler();
}
else if (text == "world") {
request();
}
});
}, 500);
})
以上是监听按钮click事件,然后是定时器,之后是ajax请求
写法可读性差,三个函数嵌套写在一起, 而且这段逻辑很难复用
用 promise写法
function listen() {
return new Promise(function (resolve, reject) {
btn.addEventListener('click', function () {
resolve()
})
})
}
function request() {
return new Promise(function (resolve, reject) {
setTimeout(() => {
resolve()
}, 500);
})
}
function ajax() {
return new Promise(function (resolve, reject) {
ajax("http://some.url.1", function response(text) {
if (text == "hello") {
resolve()
}
else if (text == "world") {
reject()
}
});
})
}
listen().then(function(){
return request()
}).then(function(){
return ajax()
}).then(function(result){
})
用promise改写之后,每个方法功能清晰,而且每个方法之后可以复用,可读性好
但promise不仅仅只是解决以上问题
回调问题
听故事
通过一个案例查看回调有什么问题 以下案例来自《你不知道的javaScript 中卷》
假设你是一名开发人员,为某个销售昂贵电视的网站建立商务结账系统。你已经做好了结 账系统的各个界面。在最后一页,当用户点击“确定”就可以购买电视时,你需要调用 (假设由某个分析追踪公司提供的)第三方函数以便跟踪这个交易。 你注意到,可能是为了提高性能,他们提供了一个看似用于异步追踪的工具,这意味着你 需要传入一个回调函数。在传入的这个 continuation 中,你需要提供向客户收费和展示感 谢页面的最终代码。 代码可能是这样:
analytics.trackPurchase( purchaseData, function(){
chargeCreditCard();
displayThankyouPage();
} );
很简单,是不是?你写好代码,通过测试,一切正常,然后就进行产品部署。皆大欢喜!
六个月过去了,没有任何问题。你几乎已经忘了自己写过这么一段代码。某个上班之前的 早晨,你像往常一样在咖啡馆里享用一杯拿铁。突然,你的老板惊慌失措地打电话过来, 让你放下咖啡赶紧到办公室。
到了办公室,你得知你们的一位高级客户购买了一台电视,信用卡却被刷了五次,他很生 气,这可以理解。客服已经道歉并启动了退款流程。但是,你的老板需要知道这样的事情 为何会出现。“这种情况你没有测试过吗?!” 你甚至都不记得自己写过这段代码。但是,你得深入研究这些代码,并开始寻找问题产生 的原因。
通过分析日志,你得出一个结论:唯一的解释就是那个分析工具出于某种原因把你的回调 调用了五次而不是一次。他们的文档中完全没有提到这种情况。
沮丧的你联系他们的客服,而客服显然和你一样吃惊。他们保证,一定会向开发者提交此 事,之后再给你回复。第二天,你收到一封很长的信,是解释他们的发现的,于是你立刻 将其转发给你的老板。
显然,分析公司的开发者开发了一些实验性的代码,在某种情况下,会在五秒钟内每秒重 试一次传入的回调函数,然后才会因超时而失败。他们从来没打算把这段代码提交到产品 中,但不知道为什么却这样做了,他们很是尴尬,充满了歉意。他们以漫长的篇幅解释了 他们是如何确定出错点的,并保证绝不会再发生同样的事故,等等。
然后呢? 你和老板讨论此事,他对这种状况却不怎么满意。他坚持认为,你不能再信任他们了(你 们受到了伤害)。对此你也只能无奈接受,并且你需要找到某种方法来保护结账代码,保 证不再出问题。 经过修补之后,你实现了像下面这样的简单临时代码,大家似乎也很满意:
var tracked = false;
analytics.trackPurchase( purchaseData, function(){
if (!tracked) {
tracked = true;
chargeCreditCard();
displayThankyouPage();
}
} );
但是,后来有一个 QA 工程师问道:“如果他们根本不调用这个回调怎么办?”哎呦!之 前你们双方都没有想到这一点。
然后,你开始沿着这个兔子洞深挖下去,考虑着他们调用你的回调时所有可能的出错情 况。这里粗略列出了你能想到的分析工具可能出错的情况:
-
调用回调过早(在追踪之前);
-
调用回调过晚(或没有调用);
-
调用回调的次数太少或太多(就像你遇到过的问题!);
-
没有把所需的环境 / 参数成功传给你的回调函数;
-
吞掉可能出现的错误或异常;
• ……
至此 故事完结
故事虽然完了 问题需要解决
故事遇到的问题是 第三方工具将你的回调函数调用了五次,虽然之后通过一个变量控制,解决了问题,但是不保证会有其他问题
回调问题
回调最大的问题是控制反转,控制反转意思是 ,回调函数式你自己的代码,但将回调函数传给第三方工具,就像故事中遇到的问题一样,将回调的控制权交给了第三方
但回调本身没有提供解决这些问题的办法,
Promise 的特性就是专门用来为这些问题提供一个有效的可复用的答案。
Promise
解决问题
那么接下来我们一个一个解决以下问题
-
调用回调过早(在追踪之前);
-
调用回调过晚(或没有调用);
-
调用回调的次数太少或太多(就像你遇到过的问题!);
-
没有把所需的环境 / 参数成功传给你的回调函数;
-
吞掉可能出现的错误或异常;
调用回调过早
我们先看个例子
function result(data) {
console.log( a );
}
var a = 0;
ajax( "..pre-cached-url..", result );
a++;
这段代码打印1 还是0 取决于ajax 是同步的还是异步的,如果是同步的,打印0,异步打印1, 如果你想要你传的回调是异步去执行的,但是第三方的ajax 确实同步的api,这就导致输出的内容和预期不符,
而promise解决了这个问题,
function result(data) {
let pormise = new Promise(function(resolve,reject){
resolve(a)
})
return promise
}
var a = 0;
ajax( "..pre-cached-url..", result );
a++;
这个时候 ,回调一定是异步执行,打印也一定是1
调用多次
promise 只能resolve一次,状态改变后不会再改变,这个问题也就随之解决了
回调未调用
我们可以用 promise的race
// 设置超时函数
// 用于超时一个Promise的工具
function timeoutPromise(delay) {
return new Promise( function(resolve,reject){
setTimeout( function(){
reject( "Timeout!" );
}, delay );
} );
}
// 设置foo()超时
Promise.race( [
foo(), // 试着开始foo()
timeoutPromise( 3000 ) // 给它3秒钟
] )
.then(
function(){
// foo(..)及时完成!
},
function(err){
// 或者foo()被拒绝,或者只是没能按时完成
// 查看err来了解是哪种情况
}
);
之后的有些问题没有写解决方法,之后补上 ,或者直接看书