JavaScript杂谈之promise揭秘

前言

所谓promise就是一个用来传递异步操作的消息,他代表了某个未来才能知道事件的结果,我们用这个代表了未来的结果先一步决定其下一步进行的操作,在ES6标准出之前我们是用笨拙的回调函数进行异步操作。

正文

事实上promise出现的意义要大得多,表面看来它只是让我们逃离了可怕的回调金字塔,让我们的代码颜值更高,语义化更强,更易于测试巴拉巴拉的一堆,但一个很多语言都坚定采用的标准有这么简单嘛?

promise与callback

至少在js里没这么简单,在我们使用笨拙的回调的时代,它实际上首先剥夺了我们正常的调用栈,作用域是混乱的,我们无法找到上层的作用域,实质上在promise中基本由promise对象和其原型链内置方法保存了上层作用域状态并传递下来,另外回调函数没有让我们使用catch、throw等差错关键字的机制,因为异步操作与回调函数之间的联系是只有参数传递的,我们要检错一般必须显式根据回调函数参数等判断错误报错

总体看来,可以近似理解为,将回调函数看做高度封装的低耦合高内聚函数,其跟外界的联系只有参数一种途径,这样设定或许可以避免很多外界影响,但同时也带来一系列不方便,我认为这才是promise引入的意义,其使用上确实相对于回调函数可能多了一些步骤,但回调函数的优点它全部拥有,缺点却被内部实现机制弥补了,其存在缺点可能就是形式上有一些繁琐,promise开始就无法结束,promise的pending无法获知结果等,这在以后将要发行的async\await的提案中得到了很大程度上的解决,我们在此就不做展开了,下面开始介绍promise特性,以及自己实现promise的构想。

promise API

先给出一个经典promise,ajax的promise实现

var getJson = function(url){
    var promise = new Promise(function (resolve, reject){
        var client = new XMLHttpRequest();
        client.open('GET', url)
        client.onreadystatechange = handler
        client.responseType = 'josn'
        client.setRequestHeader('Accept', 'application/json')
        client.send()

        function handler(){
            if (this.readyState !== 4) {
                return
            }else{
                reject(new Error(this.statusText))
            }
        }
    })

    return promise
}

getJson('/posts.json').then(function(json){
    //coding
    cosole.log('Content:' + json)
},function(error){
    console.log('出错了', error)
})

上面的代码首先封装了ajax请求json的xhr对象,返回了promise后再链式调用,如果成功了就调用第一个参数,失败了则调用第二个
promise对象比较特殊,他贯穿未来与现在,一旦创建就不受外界影响,只能等他自己变化状态。总共有三种状态,pending(进行中)这状态也就是结果还没出来,通常我们都会忽略这个状态,只关注它的结果,当然这个状态也很难观测

结果有两种状态,resolved(已完成)和rejected(已失败),给出一个异步加载图片的例子。

function loadImageAsync(url){
    return new Promise(function(resolve, reject){
        var image = new Image()

        image.onload = () => {
            resolve(image)
        }

        image.onerror = () => {
            reject(new Error('could not load image at' + url))
        }

        image.src = url
    })
}

resolve和reject函数接受参数,并将参数传给链式函数后then中的两个函数作为参数,此参数可以是数值、对象,但也可能是一个新的异步操作,如resolve( new Promise(function(){…})),reject则大多传递一个error对象实例,里面储存着异步操作失败的一些信息。

接下来就可以用then函数继续操作了,then函数中接受两个函数作为参数,分别为状态变化为resolved和rejected(可选)才运行的函数,与此同时then方法进行完毕后会返回一个新的promise实例,所以then大多采用链式写法。

resolve和reject函数接受参数,并将参数传给then中的函数作为参数,此参数可以是数值、对象,但也可能是一个新的异步操作,如resolve( new Promise(function(){…})),reject则大多传递一个error对象实例,里面储存着异步操作失败的一些信息

另外还有catch方法,实质上是.then(null, rejection),用于指定发生错误时的回调函数,下面给出一个例子

getJson('./post.json')
    .then((post) => {
        //这里是resolved状态进行的代码
    })
    .catch((error) => {
        console.log(error.info)
    })

如果对象状态变为了resolved那么就进行then中函数,否则进行catch中函数,因为promise中的错误具有冒泡的性质,会一直向后传递,直到错误被捕获为止,所以一般then都只写一个参数,错误交给catch处理,另外catch所返回的仍然是一个promise对象。

另外还有一些API是Promise.all()和Promise.race()以及一些其他方法就不详细叙述了,前两者是用于将多个promise实例包装成一个新的promise实例,有兴趣的朋友可以自己查阅资料。
要注意的是,promise构造函数接受一个函数作为参数,函数的参数有两个resolve和reject,分别可以将promise的状态相应改变。

promise ployfill

下面说一下实现的构想,主要还是理解思路,首先先明确promise的几个关键点,一个就是状态变量,一个是链式then方法,一个在高层作用域,可以被有限的几个方法更改但不能被其他作用影响,功能说清楚后,其实如何设定一目了然,闭包完全符合以上特征,当然具体如何使用还是需要斟酌,另外链式调用就不用多说了,返回的是promise实例,实例进行then方法处理,存在实例上的方法,就是放在原型链上的喽。占坑,考虑具体实现

有兴趣的可以先看promise实现深度剖析

总结

promise解决了什么问题

  1. 调用栈混乱,使用then链式API传递参数和结果
  2. 安全性问题,promise决议后不可变性,使得结果不会外部篡改
  3. 合理有序的流程控制以及检错机制

promise的局限性

  1. promise链中的错误难以捕捉,外部难以介入观察错误,catch方法又比较局限,且无finally方法
  2. promise决议值参数只有一个并且只能被决议一次,如果遇到复杂业务场景还需要进行其他逻辑处理
  3. promise有一定侵入性,首先从回调迁移到promise有一定编码习惯和重构成本,要将所有函数promise化,其次使用了promise就必须全部使用promise,最后全部promise会将所有行为(包括同步行为)都转换为异步行为
  4. promise一旦创建无法取消,暂时没有特别好的黑科技,究其原因还是promise暂时只提供了控制实例以及与相邻实例通信的功能,而promise链暂时是无法完全掌控的。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值