Promise

83 阅读11分钟

前言

在了解Promise之前,我们先来了解一下什么是回调地狱,异步任务。

异步任务

异步任务对于初学者来说可能比较陌生,与异步任务相对的是同步任务。
众所周知JS是单线程的编译语言,这也导致了他有许多的弊端,必须等到前面的一个任务结束了才能开始下一个任务,如果前面一个任务需要耗时很久,而后面的任务很快就能完成,这就会导致效率降低。为了解决这一问题,出现了异步任务。
同步任务是在主线程上排队执行的,只有前一个任务执行完毕,才能执行下一个任务。
异步任务是那些放在一边,没有进入主线程,而是进入任务队列的任务。前一个任务是否完成,不影响下一个任务的执行。

    function callback() {
        console.log('Done');
    }
    console.log('before setTimeout()');
    setTimeout(callback, 1000); // 1秒钟后调用callback函数
    console.log('after setTimeout()');

按同步任务的逻辑来看,他的输出应该是before setTimeout(),Done,*after setTimeout()*这一执行顺序,可事实并非如此。

image.png setTimeout()是一个异步函数,当代码执行到这一函数时,先将setTimeout()函数放入任务队列,当主线程中的同步任务全部执行完,也就是执行到after setTimeout()后,JS开始将异步任务推入主线程中开始执行。

回调地狱

在前端开发中,一开始为了处理异步请求的函数,我们经常在请求成功的函数内继续写函数。

function load() {
    $.ajax({
        url: 'xxx.com',
        data: 'jsonp',
        success: function(res) {
            init(res, function(res) {
                render(res, function(res) {
                    // 一层一层又一层
                });
            });
        }
    }
}

load();

当嵌套的层数越来越多的时候,出现回调函数层层嵌套的时候就会出现回调地狱
如此一层又一层的嵌套函数,会导致我们的代码可读性极差,难以重构,后期项目的维护难度非常大,最终导致出现屎山一样的代码。为了避免出现这种现象,在ES6中,我们提出了Promise函数来避免在异步请求中出现这一问题。

Promise

Promise是ES6新增的引用类型,它主要有以下三种状态。

  1. 等待态(Pending):初始状态
  2. 执行态(Fulfilled):操作完成状态
  3. 拒绝态(Rejected):操作失败状态

状态只能由Pending变为Fulfilled或是从Pending变为Fulfiilled,当状态发生改变后,就不会再发生变化。
Pending变为Fulfilled会得到一个私有的value,Pending变为Rejected会得到一个私有的reason,当Promise达到Fulfilled或是Rejected时,执行异步代码就会得到value或是reason。
Promise本身只是一个容器,真正实现异步的是它的两个回调resolved()和rejected()

1.1 基础使用

Promise的构造函数接收一个函数作为参数,这个传入的函数有两个参数resolve和*reject
resolve在函数执行成功时调用,将Promise的状态由等待态变为成功,将异步操作的结果作为参数传递过去;
reject在异步操作失败时调用,将Promise的状态由等待态变为拒绝态,将异步操作报出的错误作为参数传递出去。
在函数创建完后,可以使用then方法分别指定成功和失败的回调函数。

    let p = new Promise((resolve,reject) => {
        resolve("通过了")
    })
    p.then((data) => {
        console.log('success'+data);
    }, (error) => {
        console.log(error);
    })

image.png 当执行了reject()函数后,不会再执行后续函数

    let p = new Promise((resolve,reject) => {
        reject('拒绝了')
        resolve("通过了")
    })
    p.then((data) => {
        console.log('success'+data);
    }, (error) => {
        console.log(error);
    })

image.png

1.2 then方法

  1. then方法下一次的输出需要上一次的输入
  2. 如果一个promise执行完后,返回的还是一个promise,会把这个promise的执行结果,传递给下一次then中。
  3. 如果then中返回中返回的不是一个promise对象而是一个普通的值,则会将这个结果作为下一次then成功的结果。
  4. 如果当前的then失败了,会走向下一个then的失败。
  5. 如果返回的是undefined,不管当前是成功还是失败,都会走向下一次的成功。
  6. catch是错误没有处理的情况才会往下走
  7. then中如果不写方法则值会穿透,传递到下一个then中。

手写Promise

Promise的使用方法很简单,但为了搞懂Promise的真正过程,手写一个Promise可能更有效。

实现resolve,rejecte

在实现Promise,我们先来看一下Promise中的resolve和reject函数

    let p1 = new Promise((resolve,reject) => {
        resolve('成功')
        reject('失败')
    })
    console.log('p1',p1);

    let p2 = new Promise((resolve,reject) => {
        reject('失败')
        resolve('成功')
    })
    console.log('p2',p2);

    let p3 = new Promise((resolve,reject) => {
        throw('报错')
    })
    console.log('p3',p3);

这一段代码的输出如下

image.png

从输出我们可以看出

  1. 执行了resolve,Promise的状态就会变成fulfilled
  2. 执行了rejec,Promise的状态就会变成rejected
  3. Promise只以第一次为准,当第一次成功后就永久变成了fulfilled;当第一次失败后就永久变成了rejected
  4. Promise中有throw的话,就相当于执行了rejected

除了以上四点,我们还要知道,Promise一开始的状态是pending,并且在函数执行的过程中,我们需要将resolve和reject绑定this,这是为了防止使resolve和reject的this指向永远指向当前的Promise实例,防止因为函数执行环境的变化而变化。
现在我们可以简单的实现resolve和reject

    class myPromise {
        constructor(executor) {
            // 初始化值
            this.initValue()
            // 初始化this指向
            this.initBind()
            executor(this.resolve,this.reject)
        }

        initBind() {
            // 初始化this指向
            this.resolve = this.resolve.bind(this)
            this.reject = this.reject.bind(this)
        }

        initValue() {
            // 初试化值
            this.PromiseResult = null //终值
            this.PromiseState = 'pending' //状态
        }

        resolve(value) {
            // 如果执行resolve,则将状态变为fulfilled
            this.PromiseState = 'fulfilled'
            this.PromiseResult = value
        }

        reject(reason) {
            // 如果指向reject,则将状态变为rejected
            this.PromiseState = 'rejected'
            this.PromiseResult = reason
        }
    }

通过实例来测试一下代码

    const p1 = new myPromise((resolve,reject) => {
        resolve('成功')
    })
    console.log('p1',p1);
    const p2 = new myPromise((resolve,reject) => {
        reject('失败')
    })
    console.log('p2',p2);

输出如下
image.png 到这里,我们可以简单实现resolve和reject函数,但现在仍有很大的缺陷。当我们把测试代码改成如下。

    const p1 = new myPromise((resolve,reject) => {
        resolve('成功')
        reject('失败')
    })

按上面的逻辑,p1输出的状态应该为fulfilled,输出的值应该为成功,可事实如此吗?

image.png

从输出我们可以看出,这段代码中Promise的状态改变后是可以再变的,显然是有问题的。
在Promise中,它的状态主要有三个。

  1. pending: 初始状态,等待中。
  2. fulfilled: 成功状态。
  3. rejected: 失败状态。

将resolve和reject的代码改进如下:

        resolve(value) {
            // 判断状态,当状态改变后就不可变
            if(this.PromiseState !== 'pending') return
            // 如果执行resolve,则将状态变为fulfilled
            this.PromiseState = 'fulfilled'
            this.PromiseResult = value
        }

        reject(reason) {
            if(this.PromiseState !== 'pending') return
            // 如果指向reject,则将状态变为rejected
            this.PromiseState = 'rejected'
            this.PromiseResult = reason
        }

测试一下

    const p1 = new myPromise((resolve,reject) => {
        resolve('成功')
        reject('失败')
    })

得到输出如下

image.png 到这里我们基本上实现了resolve和reject,离实现基本的Promise只差throw了

实现throw

Promise中有throw的话,就相当于执行了reject。此时就要使用try/catch。我们可以在构造函数中添加如下代码。

        constructor(executor) {
            // 初始化值
            this.initValue()
            // 初始化this指向
            this.initBind()      
            try {
                executor(this.resolve, this.reject)
            } catch (error) {
                this.reject(error)
            }
        }

测试一下

    const p1 = new myPromise((resolve,reject) => {
        throw('失败')
    })
    console.log('p1',p1);

输出如下

image.png
至此,我们已经基本上实现了Promise的基本功能了。

then

我们使用Promise时,更多的是因为它的then方法,这个方法可以防止出现回调地狱。接下来我们来实现then方法。
我们平时使用then如下

    // 马上输出成功
    const p1 = new Promise((resolve, reject) => {
        resolve('成功')
    }).then(res => console.log(res), err => console.log(err))

    // 1秒后输出成功
    const p2 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('成功')
        }, 1000);
    }).then(res => console.log(res), err => console.log(err))

    // 链式调用 输出 2
    const p3 = new Promise((resolve, reject) => {
        resolve(1)
    }).then(res => 2 * res, err => console.log(err)).then(res => console.log(res), err => console.log(err))

从上面我们可以看出

  1. then接收两个回调,一个是成功的回调,一个是失败的回调
  2. 当Promise状态为fulfilled时,执行成功回调;当状态为rejected时,执行失败回调
  3. 当resolve和rejecte在定时器里,则当定时器结束时再执行then
  4. then支持链式调用,下一次then执行受上一次then返回值的影响

通过上面几点,我们就可以来尝试实现then

then(onFulfilled,onRejected) {
            // 接收两个回调函数onFulfilled和onRejected

            // 参数校验,确保传入的参数一定是函数
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
            onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

            if (this.PromiseState === 'fulfilled') {
                // 如果当前为成功状态,则执行第一个回调
                onFulfilled(this.PromiseResult)
            } else if (this.PromiseState === 'rejected') {
                // 如果当前状态为失败状态,则执行第二个回调
                onRejected(this.PromiseResult)
            }
        }

测试一下

    const p1 = new myPromise((resolve,reject) => {
        resolve('成功')
    }).then(res => console.log(res),err=>console.log(err))

得到输出如下

image.png 到此,我们已经基本上实现了then函数的功能,但当我们遇到计数器时,如何等到计时器结束后才去执行then里面的回调。
这一问题,一开始我也不懂,后面看了大神的讲解,我们不能确保1秒以后才执行then函数,但我们可以保证1秒以后再去then里面的回调。这句话听起来确实很蒙蔽,大神的讲解如下。

image.png 我们可以在1秒内,先把then里面的两个回调给保存下来,等到1秒后,执行了resolve或reject,再去判断状态,并且去判断要去执行刚刚保存的两个回调中的哪一个。
当Promise的状态为pending时,就代表定时器还没跑完,当计时器跑完,Promise的状态一定是fulfilledrejected
在JS中用来保存数据的要么是对象,要么是数组,这里如果用对象的话,当执行一个then就创建一个对象的话,then执行太多的话创建的对象会变得非常多,占用非常多的内存。所以我们尝试用数组来保存数据。 总体代码如下:

    class myPromise {
        constructor(executor) {
            // 初始化值
            this.initValue()
            // 初始化this指向
            this.initBind()    
            // 保存成功的回调
            this.onFulfilledCallbacks = []
            // 保存失败的回调
            this.onRejectedCallbacks = []  
            try {
                executor(this.resolve, this.reject)
            } catch (error) {
                this.reject(error)
            }
        }

        initBind() {
            // 初始化this指向
            this.resolve = this.resolve.bind(this)
            this.reject = this.reject.bind(this)
        }

        initValue() {
            // 初试化值
            this.PromiseResult = null //终值
            this.PromiseState = 'pending' //状态
        }

        resolve(value) {
            // 判断状态,当状态改变后就不可变
            if (this.PromiseState !== 'pending') return
            // 如果执行resolve,则将状态变为fulfilled
            this.PromiseState = 'fulfilled'
            this.PromiseResult = value
            // 执行保存成功的回调
            while(this.onFulfilledCallbacks.length) {
                this.onFulfilledCallbacks.shift()(this.PromiseResult)
            }
        }

        reject(reason) {
            if (this.PromiseState !== 'pending') return
            // 如果指向reject,则将状态变为rejected
            this.PromiseState = 'rejected'
            this.PromiseResult = reason
            // 执行保存失败的回调
            while(this.onRejectedCallbacks.length) {
                this.onRejectedCallbacks.shift()(this.PromiseResult)
            }
        }

        then(onFulfilled,onRejected) {
            // 接收两个回调函数onFulfilled和onRejected

            // 参数校验,确保传入的参数一定是函数
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
            onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

            if (this.PromiseState === 'fulfilled') {
                // 如果当前为成功状态,则执行第一个回调
                onFulfilled(this.PromiseResult)
            } else if (this.PromiseState === 'rejected') {
                // 如果当前状态为失败状态,则执行第二个回调
                onRejected(this.PromiseResult)
            } else if (this.PromiseState === 'pending') {
                // 如果状态为待定状态,则暂时保存两个回调
                this.onFulfilledCallbacks.push(onFulfilled.bind(this))
                this.onRejectedCallbacks.push(onRejected.bind(this))
            }
        }


    }

测试一下

    const p1 = new myPromise((resolve,reject) => {
        setTimeout(() => {
            resolve('成功')
        }, 1000);
    }).then(res => console.log(res),err=>console.log(err))

输出如下

image.png 在1秒后,浏览器输出成功。

then链式调用

离最后实现Promise,现在只剩下then的链式调用。
then的链式调用受上一个then返回值的影响。
then的链式调用如下:

    const p1 = new Promise((resolve, reject) => {
        resolve(10)
    }).then(res => 2 * res,  err => console.log(err)).then(res => console.log(res), err => console.log(err))

输出如下 image.png 从上面的例子我们可以看出

  1. then方法本身会返回一个新的Promise对象。
  2. 如果返回值是一个Promise对象,返回值为成功,则新Promise的状态就是成功。
  3. 如果返回值是一个Promise对象,返回值为失败,则新Promise的状态就是失败。
  4. 如果返回值是一个非Promise对象,新Promise对象就是成功,值为返回值。

then实现链式调用最重要的是,then执行后返回一个Promise对象
then代码改进如下:

then(onFulfilled, onRejected) {
            // 接收两个回调函数onFulfilled和onRejected

            // 参数校验,确保传入的参数一定是函数
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
            onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

            var thenPromise = new myPromise((resolve, reject) => {
                const resolvePromise = cb => {
                    try {
                        const x = cb(this.PromiseResult)
                        if (x === thenPromise) {
                            // 不能返回自身
                            throw new Error('不能返回自身')
                        }
                        if (x instanceof myPromise) {
                            // 如果返回值是Promise对象,返回值为成功,新的Promise就是成功
                            // 如果返回值是Promise对象,返回值为失败,新的Promise就是失败
                            x.then(resolve, reject)
                        } else {
                            // 非Promise对象直接成功
                            resolve(x)
                        }
                    } catch (err) {
                        reject(err)
                        throw new Error(err)
                    }
                }


                if (this.PromiseState === 'fulfilled') {
                    // 如果当前为成功状态,则执行第一个回调
                    // onFulfilled(this.PromiseResult)
                    resolvePromise(onFulfilled)
                } else if (this.PromiseState === 'rejected') {
                    // 如果当前状态为失败状态,则执行第二个回调
                    onRejected(this.PromiseResult)
                } else if (this.PromiseState === 'pending') {
                    // 如果状态为待定状态,则暂时保存两个回调
                    this.onFulfilledCallbacks.push(onFulfilled.bind(this))
                    this.onRejectedCallbacks.push(onRejected.bind(this))
                }

            })
            return thenPromise

        }

测试一下

    const p1 = new myPromise((resolve, reject) => {
        resolve(100)
    }).then(res => res * 100, err => console.log(err)).then(res => console.log(res), err => console.log(err))

输出如下:

image.png

总结

至此,我们简单的实现了Promise,由于我本人比较菜,整篇文章基本上都是看别的大神的文章总结出来的。