Promise
的重要性应该不用多说,大家用的频率也很高,因此有必要深入了解一下。
Promise应运而生
在实际的使用当中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最重要也是最主要的一个场景就是ajax请求。通俗来说,由于网速的不同,可能你得到返回值的时间也是不同的,这个时候我们就需要等待,结果出来了之后才知道怎么样继续下去。
1 | // 简单的ajax原生实现 |
在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后我们才能开始处理数据。
这样做看上去并没有什么麻烦,但是如果这个时候,我们还需要做另外一个ajax请求,这个新的ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候我们就不得不如下这样做:
1 | var url = 'http://someRequest'; |
当出现第三个ajax(甚至更多)仍然依赖上一个请求的时候,我们的代码就变成了一场灾难。这场灾难,往往也被称为回调地狱。当然,除了回调地狱之外,还有一个非常重要的需求:为了我们的代码更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来。
因此我们需要一个叫做Promise的东西,来解决这个问题。
Promise基本概念
Promise对象有三种状态,他们分别是:
- pending: 等待中,或者进行中,表示还没有得到结果
- resolved(fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
- rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行
这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。在Promise对象的构造函数中,将一个函数作为第一个参数。而这个函数,就是用来处理Promise的状态变化。
1 | new Promise(function(resolve, reject) { |
Promise对象中的then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法,第一个then方法完成以后,会将返回结果作为参数,传入第二个then方法。
1 | promise('aaa') |
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
1 | promise('aaa').then(function() { |
注意,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。因此一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
1 | // 生成一个Promise对象的数组 |
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
对ajax进行封装
OK,了解了这些基础知识之后,我们再回过头,利用Promise的知识,对最开始的ajax的例子进行一个简单的封装。看看会是什么样子。
1 | var url = 'http://someRequest'; |
为了健壮性,处理了很多可能出现的异常,总之,就是正确的返回结果,就resolve一下,错误的返回结果,就reject一下。并且利用上面的参数传递的方式,将正确结果或者错误信息通过他们的参数传递出来。
实现一个简单的Promise
看到这里,我们已经对Promise有了大概的认识,接着我们可以试着自己实现一个简易版的Promise,加深对它的理解。
1 | function Promise(fn) { |
此时已经有基本的resolve和then了,再实现then的链式调用
1 | this.then = function (onFulfilled) { |
以上代码存在一个明显的问题:如果在then方法注册回调之前,resolve函数就执行了,那then的回调函数将不会执行,且Promise异步操作成功之后调用的then注册的回调也不会执行了。因此加入延时机制,还需要加入状态:
1 | function Promise(fn) { |
我们暂时忽略了错误处理机制,为的使我们更关注于核心代码上面去
相关阅读:Promise源码