es6-Promise 再次学习

303 阅读6分钟

之前写过文章,介绍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来了解是哪种情况
 }
);

之后的有些问题没有写解决方法,之后补上 ,或者直接看书