Promise从认识到实现

126 阅读8分钟

Promise简介

背景

我们都知道Promise是用来代替回调函数解决异步问题。在Promise出现之前,回调函数用法为:

document.addEventListener('click',callback)

上述的事件绑定非常常见,理解起来也没有任何问题,实际上现在也非常常用,但是如果是下面这种情况呢:

async(1, function(value){
  async(value, function(value){
    async(value, function(value){
      async(value, function(value){
        async(value, function(value){
          async(value, final);
        });
      });
    });
  });
});

当一个异步调用依赖另一个异步调用的回调函数时,就会出现回调嵌套,一旦这种依赖层级过深,就会出现所谓的 ”回调地狱“。Promise的出现,可以很好的解决这个问题。

Promise表示承诺、许诺的意思,意思是使用了Promise之后他肯定会给我们答复,无论成功或者失败都会给我们一个答复,在《Javascript高级程序设计》第四版中,正式将Promise翻译成”期约“,表示约定未来发生的事,这个翻译从一定程度上解释了Promise的本意。

Promise 规范有很多,如Promise/APromise/BPromise/D 以及 Promise/A 的升级版 Promise/A+ES6 中采用了 Promise/A+ 规范。

Pomise/A+规范

  1. 一个promise必须有3个状态,pending,fulfilled(resolved),rejected。pending表示进行中,resolved表示完成,rejected表示失败,当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。promise英文译为承诺,也就是说promise的状态一旦发生改变,就永远是不可逆的。

(2)一个promise必须有一个then方法,then方法接受两个参数:

promise.then(onFulfilled,onRejected)

其中onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法,而onRejected表示状态从pending——>rejected所执行的方法。

(3)为了实现链式调用,then方法必须返回一个promise,并且then方法可以被同一个 promise 调用多次。(即注册多个回调函数)

API

1.构造函数

function Promise(resolver) {}

2.原型方法

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}

3.静态方法

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}

Pomise使用

promise使用非常简单,通过new操作创建Promise对象,Promise构造函数接收一个executor立即执行函数,该函数接收两个参数:resolve和reject。resolve函数被调用时会将pending状态转化成fullfilled状态,然后触发then方法中的第一个回调;同理,reject函数会将pending状态转化成rejected状态,然后触发then方法中的第二回调,或者catch回调。

const promise = new Promise(function(resolve, reject) {    
    //待处理的异步逻辑    
    //处理结束后,调用resolve或reject方法
})
promise.then(result => {     
    /*     这是thenable函数, 如果当前的Promise已经是resolved状态, 该函数会立即执行,如果当前是未决状态, 则会加入作业队列, 等待Promise状态变为resolved会立即执行     */ 
}, 
err => {     
    /*     这是catchable函数, 如果当前的Promise已经是rejected状态, 该函数会立即执行, 如果当前是未决状态, 则会加入作业队列, 等待Promise状态变为rejected会立即执行     */ 
})

alt promise图解

下面是一个简单的例子:

var myPromise1 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		console.log('----async1 start');
		resolve('async1 resolved');
	},100)
})
myPromise1.then((res)=>{
        console.log(res);
        console.log('----async1 end')
    },
    (err)=>{
	console.log(err)
	console.log('----async1 end')
})

then可以用来注册thenable函数和catchable函数,而catch只能注册catchable函数。

**resolve方法的参数除了正常的值以外,还可能是另一个Promise实例。**例如,p1和p2都是Promise的实例,但是p2的resolve方法将p1作为参数,这时p1的状态就会传递给p2。如果调用的时候,p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态已经是fulfilled或者rejected,那么p2的回调函数将会立刻执行。

链式调用

正由于前面所说,Promise.prototype.then方法返回的是一个新的Promise对象,因此可以采用链式写法。

promise1
.then(function(json) {  
    return json.name;
})
.then(function(name) {  
    // proceed
});

第一个回调函数完成以后,会将返回结果作为参数传入第二个回调函数

但是如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。

错误捕获

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的别名,用于指定发生错误时的回调函数。

//reject 捕获new Promise((resolve,reject)=>{    throw new Error('error');})
.then(()=>{    console.log('resolve'); },
 ()=>{    console.log('reject'); })
.catch(()=>{    console.log('catch');});

//catch捕获new Promise((resolve,reject)=>{    reject('error');})
.then(()=>{    console.log('resolve');})
.catch(()=>{    console.log('catch');});

throw和reject都可被fail和catch方法捕捉,主要区别是throw不能用在异步中,而reject可以

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

Promise.all

const promise = Promise.all([p1, p2, p3]);

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

该方法在处理多个异步请求时非常有用,例如一个页面需要请求多个资源,只有在所有请求都完成后才会显示。

Promise.race

const promise = Promise.race([p1, p2, p3]);

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。不同的是,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。率先改变的 Promise 实例的返回值,作为参数传递给p的回调函数。这也是为什么叫race(赛跑)。

Promise.resolve(args)

该方法返回一个resolved状态的promise,参数作为状态数据。

Promise.reject(args)

该方法返回一个rejected状态的promise,参数作为状态数据。

总结

  1. `Promise`的状态一经改变就不能再改变。
  2. `.then`和`.catch`都会返回一个新的`Promise`。
  3. `catch`不管被连接到哪里,都能捕获上层的错误。
  4. 在`Promise`中,返回任意一个非 `promise` 的值都会被包裹成 `promise` 对象,例如`return 2`会被包装为`return Promise.resolve(2)`。
  5. `Promise` 的 `.then` 或者 `.catch` 可以被调用多次, 当如果`Promise`内部的状态一经改变,并且有了一个值,那么后续每次调用`.then`或者`.catch`的时候都会直接拿到该值。
  6. `.then` 或者 `.catch` 中 `return` 一个 `error` 对象并不会抛出错误,所以不会被后续的 `.catch`捕获。
  7. `.then` 或 `.catch` 返回的值不能是 promise 本身,否则会造成死循环。
  8. `.then` 或者 `.catch` 的参数期望是函数,传入非函数则会发生值穿透。
  9. `.then`方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为`catch`是`.then`第二个参数的简便写法。
  10. `.finally`方法也是返回一个`Promise`,他在`Promise`结束的时候,无论结果为`resolved`还是`rejected`,都会执行里面的回调函数。

Promise实现

var Promise = (function() {
    function Promise(resolver) {
        if (typeof resolver !== 'function') { //resolver必须是函数
            throw new TypeError('Promise resolver ' + resolver + ' is not a function')
        }
        if (!(this instanceof Promise)) return new Promise(resolver)

        var self = this //保存this
        self.callbacks = [] //保存onResolve和onReject函数集合
        self.status = 'pending' //当前状态

        function resolve(value) {
            setTimeout(function() { //异步调用
                if (self.status !== 'pending') {
                    return
                }
                self.status = 'resolved' //修改状态
                self.data = value

                for (var i = 0; i < self.callbacks.length; i++) {
                    self.callbacks[i].onResolved(value)
                }
            })
        }

        function reject(reason) {
            setTimeout(function(){ //异步调用
                if (self.status !== 'pending') {
                    return
                }
                self.status = 'rejected' //修改状态
                self.data = reason

                for (var i = 0; i < self.callbacks.length; i++) {
                    self.callbacks[i].onRejected(reason)
                }
            })
        }

        try{
            resolver(resolve, reject) //执行resolver函数
        } catch(e) {
            reject(e)
        }
    }

    function resolvePromise(promise, x, resolve, reject) {
        var then
        var thenCalledOrThrow = false

        if (promise === x) {
            return reject(new TypeError('Chaining cycle detected for promise!'))
        }

        if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
            try {
                then = x.then
                if (typeof then === 'function') {
                    then.call(x, function rs(y) {
                        if (thenCalledOrThrow) return
                        thenCalledOrThrow = true
                        return resolvePromise(promise, y, resolve, reject)
                    }, function rj(r) {
                        if (thenCalledOrThrow) return
                        thenCalledOrThrow = true
                        return reject(r)
                    })
                } else {
                    return resolve(x)
                }
            } catch(e) {
                if (thenCalledOrThrow) return
                thenCalledOrThrow = true
                return reject(e)
            }
        } else {
            return resolve(x)
        }
    }

    Promise.prototype.then = function(onResolved, onRejected) {
        //健壮性处理,处理点击穿透
        onResolved = typeof onResolved === 'function' ? onResolved : function(v){return v}
        onRejected = typeof onRejected === 'function' ? onRejected : function(r){throw r}
        var self = this
        var promise2

        //promise状态为resolved
        if (self.status === 'resolved') {
            return promise2 = new Promise(function(resolve, reject) {
                setTimeout(function() {
                    try {
                        //调用then方法的onResolved回调
                        var x = onResolved(self.data)
                        //根据x的值修改promise2的状态
                        resolvePromise(promise2, x, resolve, reject)
                    } catch(e) {
                        //promise2状态变为rejected
                        return reject(e)
                    }
                })
            })
        }

        //promise状态为rejected
        if (self.status === 'rejected') {
            return promise2 = new Promise(function(resolve, reject) {
                setTimeout(function() {
                    try {
                        //调用then方法的onReject回调
                        var x = onRejected(self.data)
                        //根据x的值修改promise2的状态
                        resolvePromise(promise2, x, resolve, reject)
                    } catch(e) {
                        //promise2状态变为rejected
                        return reject(e)
                    }
                })
            })
        }

        //promise状态为pending
        //需要等待promise的状态改变
        if (self.status === 'pending') {
            return promise2 = new Promise(function(resolve, reject) {
                self.callbacks.push({
                    onResolved: function(value) {
                        try {
                            //调用then方法的onResolved回调
                            var x = onResolved(value)
                            //根据x的值修改promise2的状态
                            resolvePromise(promise2, x, resolve, reject)
                        } catch(e) {
                            //promise2状态变为rejected
                            return reject(e)
                        }
                    },
                    onRejected: function(reason) {
                        try {
                            //调用then方法的onResolved回调
                            var x = onRejected(reason)
                            //根据x的值修改promise2的状态
                            resolvePromise(promise2, x, resolve, reject)
                        } catch(e) {
                            //promise2状态变为rejected
                            return reject(e)
                        }
                    }
                })
            })
        }
    }

    //获取当前Promise传递的值
    Promise.prototype.valueOf = function() {
        return this.data
    }

    //由then方法实现catch方法
    Promise.prototype.catch = function(onRejected) {
        return this.then(null, onRejected)
    }

    //finally方法
    Promise.prototype.finally = function(fn) {
        return this.then(function(v){
            setTimeout(fn)
            return v
        }, function(r){
            setTimeout(fn)
            throw r
        })
    }

    Promise.prototype.spread = function(fn, onRejected) {
        return this.then(function(values) {
            return fn.apply(null, values)
        }, onRejected)
    }

    Promise.prototype.inject = function(fn, onRejected) {
        return this.then(function(v) {
            return fn.apply(null, fn.toString().match(/\((.*?)\)/)[1].split(',').map(function(key){
                return v[key];
            }))
        }, onRejected)
    }

    Promise.prototype.delay = function(duration) {
        return this.then(function(value) {
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    resolve(value)
                }, duration)
            })
        }, function(reason) {
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    reject(reason)
                }, duration)
            })
        })
    }

    Promise.all = function(promises) {
        return new Promise(function(resolve, reject) {
            var resolvedCounter = 0
            var promiseNum = promises.length
            var resolvedValues = new Array(promiseNum)
            for (var i = 0; i < promiseNum; i++) {
                (function(i) {
                    Promise.resolve(promises[i]).then(function(value) {
                        resolvedCounter++
                        resolvedValues[i] = value
                        if (resolvedCounter == promiseNum) {
                            return resolve(resolvedValues)
                        }
                    }, function(reason) {
                        return reject(reason)
                    })
                })(i)
            }
        })
    }

    Promise.race = function(promises) {
        return new Promise(function(resolve, reject) {
            for (var i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then(function(value) {
                    return resolve(value)
                }, function(reason) {
                    return reject(reason)
                })
            }
        })
    }

    Promise.resolve = function(value) {
        var promise = new Promise(function(resolve, reject) {
            resolvePromise(promise, value, resolve, reject)
        })
        return promise
    }

    Promise.reject = function(reason) {
        return new Promise(function(resolve, reject) {
            reject(reason)
        })
    }

    Promise.fcall = function(fn){
        // 虽然fn可以接收到上一层then里传来的参数,但是其实是undefined,所以跟没有是一样的,因为resolve没参数啊
        return Promise.resolve().then(fn)
    }

    Promise.done = Promise.stop = function(){
        return new Promise(function(){})
    }

    Promise.deferred = Promise.defer = function() {
        var dfd = {}
        dfd.promise = new Promise(function(resolve, reject) {
            dfd.resolve = resolve
            dfd.reject = reject
        })
        return dfd
    }

    try { // CommonJS compliance
        module.exports = Promise
    } catch(e) {}

    return Promise
})()

参考:

www.jianshu.com/p/b4f0425b2…

mengera88.github.io/2017/05/18/…

mp.weixin.qq.com/s/1WnyQnaiz…

www.jianshu.com/p/5833a2ae8…