定义
Promise 对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。
Promise:为构造函数,接受一个函数作为参数,该函数接收resolve和reject两个参数,由JavaScript引擎提供,无需自己实现。
resolve:其作用是将Promise对象的状态从Pending变为Resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject:其作用是将Promise对象的状态从从Pending变为Rejected,在异步操作失败时调用,并将异步操作抛出的错误,作为参数传递出去。
Promise对象只有三种状态:
- 未完成:pending
- 已完成:resolved(也称fulfilled)
- 失败:rejected
以上三种状态只有以下两种变化,一旦当前状态变为“resolved”或“rejected”,就意味着不会再有新的状态变化:
- pending->resolved
- pending->rejected
then(onFulfilled, onRjected):then方法传递两个函数作为参数,当Promise状态为fulfilled时,调用 then 的 onFulfilled 方法,当Promise状态为rejected时,调用 then 的 onRjected 方法。
catch(onRjected):当Promise状态为rejected时,调用 then 的 onRjected 方法。
例子:
1 | let p = new Promise(function(resolve, reject){ |
原理
根据以上内容,Promise对象作为一个构造函数,并接受一个函数作为参数,该函数执行完同步或异步操作后,调用它的两个参数resolve和reject,,同时其内部有状态机制,原型链上有then方法,故可先初步实现Promise的功能,如下:
1 | function Promise(executor){ //executor是一个执行器(函数) |
测试下:
1 | // 成功 |
以上可以看到Promise传入参数的函数是立刻执行的,当传入的函数执行异步操作时,这个时候then方法中回调是不会异步执行的,即此时获取不到任何结果,例如:
1 | let p = new Promise(function(resolve, reject){ |
原因是then只是对成功或者失败状态进行了判断,而Promise实例化时,传入的函数会立即执行,而setTimeout是异步执行的,当then方法执行的时候,此时Promise的状态还是pendding,因此也需要对pendding状态进行判断,此时我们可以在then方法中判断pendding状态,先获取保存回调函数,等到异步函数执行再一一执行then方法中的回调函数,改进后的代码如下:
- 实现异步
1 | // myPromise |
- 处理错误
Promise捕获错误执行的是reject函数,因此在实例中跑出错误时,只执行reject函数就可以,可使用try catch进行异常捕获;
1 | try{ |
- catch方法实现
catch方法其实就是获取实例rejected方法捕获错误,相当于执行then(null, onRejected)
1 | Promise.prototype.catch = function(onRejected) { |
- 实现then的链式调用
上面实现的then方法其实还不完善,不能进行链式调用。是否可以跟jq一样在then方法中return this实现链式调用,可实际上Promise中then方法要复杂的多,不能简单的用return this实现链式调用。
1 | let p1 = new Promise(function(resolve, reject){ |
按照上面代码,如果then方法中返回的是this,那么p2跟p1相同,固状态也相同,但是Promise的成功态和失败态不能相互转换,那就不会得到p1成功而p2失败的效果,而实际上是可能发生这种情况的。因此Promise的then方法实现链式调用的原理是:返回一个新的Promise
按照这样的思路,在then方法中先定义一个新的Promise,取名为promise2,然后在三种状态下分别用promise2包装一下,在调用onFulfilled时用一个变量x(规定的)接收返回值,try…catch…一下代码,没错就调resolve传入x,有错就调reject传入错误,最后再把promise2给return出去。
1 | // 改动then |
这样实现x只是当做一个普通的值,比如字符串数字数组对象等值可以用上面代码简单链式调用传给下一个then,但是如果x也是一个Promise或者是其他异步操作,可能性如下:
- 前一次then返回一个普通值,比如字符串数组对象等,只需传给下一个then,刚才的方法就能实现。
- 前一次then返回的是一个Promise,是正常的操作,也是Promise提供的语法糖,我们要想办法判断到底返回的是啥。
- 前一次then返回的是一个Promise,其中有异步操作,也是理所当然的,那我们就要等待他的状态改变,再进行下面的处理。
- 前一次then返回的是自己本身这个Promise
1 | var p1 = p.then(function(){ |
- 前一次then返回的是自定义的Promise的普通对象,比如{then:‘xxx’},或者改变then方法的访问器属性,在then里故意抛错。比如
1 | let promise = {} |
- 在resolve传一个Promise对象
1 | p.then(function(data) { |
- 同时调用resolve和reject,则需取前面调用的方法,忽略后一个
- 不传任何参数,下面代码运行结果是undefined,按我们上面实现的代码,then不传任何参数时,resolve中传的值是没有办法穿透的,
1 | new Promise(resolve=>resolve(8)) |
针对以上问题可以对then方法代码做出优化:
1 | Promise.prototype.then = function (onFulfilled, onRjected) { |
- 其他方法
1 | // 捕获错误的方法,在原型上有catch方法,返回一个没有resolve的then结果即可 |