likes
comments
collection
share

【ES6】Promise:从语法到手写

作者站长头像
站长
· 阅读数 7

什么是Promise

Promise是ES6中提出的一个处理异步的方法。其本身是一个构造函数,可以获取异步的操作。简单来说,Promise是一个可以表示异步操作成功或者失败的对象。

Promise内部会维护三种状态(state):'pending'(默认状态)'fulfilled'(异步操作成功)'rejected'(异步操作失败)。它们是异步操作状态的标识。

语法

初始化

Promise是一个构造函数,创建一个它的实例对象:

const promise = new Promise((resolve,reject) => {
    //如果异步操作成功
    resolve(value)
    //如果异步操作不成功
    reject(error)
})

Promise构造函数接受一个回调,且resolve,reject两个函数会作为参数传给回调函数。注意这两个函数v8引擎会自动提供,不用自己部署。

resolve函数是异步操作成功的表示,如果回调中逻辑能够执行成功,就会调用resolve函数,并且将参数传出去。reject函数是异步操作失败的表示,如果回调中逻辑无法执行成功,就会调用reject函数,并且将参数传出去。

一般来说,调用resolvereject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolvereject的后面。

Promise.prototype.then

Promise身上的then方法写在原型对象(Promise.prototype)上。then方法接受两个回调函数,第一个是在resolve调用后执行,第二个是在reject调用后执行。当然其核心还是要根据当前Promise对象身上的状态变更情况,在后面的手写部分大家会更加理解。

then方法会返回一个新的Promise对象实例,因此可以采用链式写法,即then方法后面再调用另一个then方法。

下面给大家举个实例:

function a () {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('a');
            resolve('1')
        })
    })
}

a()
.then(
    (res) => {
        console.log(res);
    },
    (error) => {
        console.log(error);
    }
)

函数ab的执行结果都是一个Promise实例,a中Promise对象接受的回调中调用了resolve,所以下面的.then中执行第一个回调函数,且res就是resolve传入的参数,所以打印结果是'a' 然后是'1'。通过这种方式巧妙的控制了异步操作的顺序。

注意:不要在 then 回调中调用 resolve:由于 Promise 内部已经处理了状态和值的传递,因此在 then 方法的回调中手动调用 resolve 是多余的且可能会导致问题。

Promise.prototype.catch

该函数用于指定发生错误时的回调函数。 效果和.then中的第二个回调很类似,区别就是使用.catch捕获错误也能捕获.then中的错误,范围更广。

该方法也会返一个Promise实例,后面依然可接.then。

实例:

function a () {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('a');
            reject('1')
        })
    })
}

a()
.then(res => {
    console.log(res);
})
.catch(error => {
    console.log(error) //打印'1'
})

Promise.prototype.finally

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。且该方法的回调不接受任何参数

实例:

function a () {
    return new Promise((resolve,reject) => {
        reject('1')
    })
}

a()
.then(res => {
    console.log(res);
})
.catch(error => {
    console.log(error)
})
.finally(() => {
    console.log('结束') //打印'结束'
})

Promise.all

Promise.all方法会可以用于多个Promise的实例,包装成一个新的 Promise 实例。一般接受一个数组作为参数,数组中的每一个元素都要为Promise的实例,不接数组也可以,但是要具有 Iterator 接口,且返回每一个成员都是Promise实例。如果元素不为Promise实例,就会调用resolve函数转为实例。

Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
})

该方法有两个特点:

  1. Promise.all作用的多个Promise实例,必须所有的实例的状态都为'fulfilled',或者说都会成功调用resolve才会执行then方法的第一个回调,有一个实例的状态为'rejected'就执行catch方法,或者then的第二个回调。

  2. 由p1、p2、p3返回值中resolve的参数组成的数组传给.then中的回调函数。但是注意Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的。这个规则不会被异步操作影响,也就是:即使p1的结果获取的比p2的要晚,p1的返回数据也会在p2的前面。

实例:

function a(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('a');
            resolve('a')
        },1000)
    })
}
function b(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('b');
            resolve('b')
        },500)
    })
}
Promise.all([a(),b()]).then((res) =>{
    console.log(res);
})

//执行结果
//b
//a
//[ 'a', 'b' ]

那么很明显,a函数的执行结果比b函数的结果晚,所以先打印'b',再去打印'a'。但对于resolve的参数组成的数组要与传入数组的顺序一致,所以res的值为 ['a','b']

Promise.race

该方法会可以用于多个Promise的实例,包装成一个新的 Promise 实例。接受的参数与Promise.all一致。

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

特点:数组中Promise实例的状态谁改变的快,then方法接受谁传递过来的值,并且立即执行回调。这意味着无论是成功状态还是失败状态,race 都会立即执行 then 方法,不会等待其他 Promise 实例的状态改变。

实例:

function a(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('a');
            resolve('a更快')
        },1000)
    })
}
function b(){
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('b');
            resolve('b更快')
        },500)
    })
}
Promise.race([a(),b()]).then((res) =>{
    console.log(res);
    // c()
})

//执行结果
//b
//b更快
//a

手写一个Promise

完整的代码:

class MyPromise {
    constructor(executor) { //接受一个回调
        this.state = 'pending'  // promise的状态,也就是开关变量
        this.value = undefined // 接收resolve的参数,不传默认为undefined
        this.reason = undefined // 接收reject的参数
        this.onFulfilledCallbacks = [] // 数组? 可能有多个.then
        this.onRejectedCallbacks = []

        const resolve = (value) => {
            if(this.state === 'pending') { //先判断状态
                this.state = 'fulfilled'
                this.value = value  //储存外部传入的参数
                // 状态变更完成,把then里面的回调触发掉
                this.onFulfilledCallbacks.forEach(cb => cb(value))
            }
        }

        const reject = (reason) => {
            if(this.state === 'pending') {
                this.state = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach(cb => cb(reason))
            }
        }
        executor(resolve,reject)
    }

//1. .then
    then(onFulfilled, onRejected) { //接受两个回调
        // then没有资格调用 onFulfilled()
        // 把 onFulfilled() 存起来, 供resolve 调用

        //先判断形参是不是函数体
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected  === 'function' ? onRejected  : reason => { throw reason }

        // 然后返回一个promise对象
        const newPromise = new MyPromise((resolve,reject) => {
            //注意这里的状态是指.then被调用时,而不是内部回调触发时
            if(this.state === 'fulfilled'){
                // then前面的promise对象状态是同步变更完成了,就不用存起来了,直接触发回调函数
                // 例如promise对象里面的resolve没有被setTimeout包裹
                setTimeout(() => { // 官方是微任务,我们用宏任务简化一下
                    try{
                        // 第一个.then有返回值就用它的返回值,没有就用默认的undefined
                        // 
                        const result = onFulfilled(this.value)
                        //原本应该放result里面的resolve中的参数
                        //但是因为会异步
                        resolve(result)
                    }catch (error) {
                        reject(error)
                    }
                })
            }
            if(this.state === 'rejected') {
                setTimeout(() => {
                    try{
                        const result = onRejected(this.reason)
                        //因为要保证后面的.then不被影响
                        //所以还用resolve
                        resolve(result)
                    }catch (error) {
                        reject(error)
                    }
                })
            }

            if(this.state === 'pending'){ //缓存then中的回调
                this.onFulfilledCallbacks.push((value) => { 
                    setTimeout(() => { // 保障将来onFulfilled在resolve中被调用时是一个异步函数
                        try{
                            const result = onFulfilled(value)
                            resolve(result)
                        }catch(error){
                            reject(error)
                        }
                    })
                })
                this.onRejectedCallbacks.push((reason) => { 
                    setTimeout(() => { // 保障将来onFulfilled在resolve中被调用时是一个异步函数
                        try{
                            const result = onRejected(reason)
                            resolve(result)
                        }catch(error){
                            reject(error)
                        }
                    })
                })
            }
        })
        return newPromise
    }
//2. race
    static race (promises) { //不被实例对象访问,不是原型上的方法
        return new MyPromise((resolve,reject) => {
            // 看promises里面的哪个对象的状态先变更,谁先就拿谁的value或者reason
            for(let promise of promises){
                promise.then(
                    (value) => {
                        resolve(value)
                    },
                    (reason) => {
                        reject(reason)
                    }
                )
            }
        })
    }
//3. all
    static all(promises){
        return new MyPromise((resolve,reject) =>{
            let count = 0, arr = []
            for( let i = 0; i< promises.length; i++) {
                promises[i].then(
                    (value) => {
                        count++
                        arr[i] = value
                        if(count === promises.length) {
                            resolve(arr)
                        }
                    },
                    (reason) =>{
                        reject(reason)
                    }
                )
            }
        })
    }
}

手写Promise.prototype.then

思路:

  1. 类型判断:then方法接受的参数一定要是函数类型。

  2. 判断then方法调用时的状态。注意这里的状态可能有三种,要根据调用then方法的函数内部逻辑,是同步代码还是异步代码。如果是异步代码,那么调用then方法时状态依然是'pending',如果是同步,那么状态就很可能已经发生变更。

  3. 根据不同的状态去执行then里面的回调

    • 如果是'pending'状态,说明要等待状态变更完之后才能执行回调,且可能有多个then链式调用,所以要用一个数组来存储,变更完成后批量化执行回调。this.onFulfilledCallbacks储存成功的结果(then方法第一个回调),this.onRejectedCallbacks存储失败的结果(then方法第二个回调)
    • 如果是'fulfilled''rejected'状态,说明可以立即执行回调,不需要存入数组。
  4. 使用setTimeout模拟异步微任务。回调函数的执行结果要resolve出去,供下一个then使用。

  5. 最后返回一个新的Promise对象以供链式调用。

代码:

then(onFulfilled, onRejected) {
        //先判断形参是不是函数体
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected  === 'function' ? onRejected  : reason => { throw reason }
        // 然后返回一个promise对象
        const newPromise = new MyPromise((resolve,reject) => {
            //注意这里的状态是指.then被调用时,而不是内部回调触发时
            if(this.state === 'fulfilled'){
                setTimeout(() => { //使用setTimeout
                    try{
                        const result = onFulfilled(this.value)
                        resolve(result)
                    }catch (error) {
                        reject(error)
                    }
                })
            }
            if(this.state === 'rejected') {
                setTimeout(() => {
                    try{
                        const result = onRejected(this.reason)
                        //因为要保证后面的.then不被影响
                        resolve(result)
                    }catch (error) {
                        reject(error)
                    }
                })
            }
            if(this.state === 'pending'){ //缓存then中的回调
                this.onFulfilledCallbacks.push((value) => { 
                    setTimeout(() => { // 保障将来onFulfilled在resolve中被调用时是一个异步函数
                        try{
                            const result = onFulfilled(value)
                            resolve(result)
                        }catch(error){
                            reject(error)
                        }
                    })
                })
                this.onRejectedCallbacks.push((reason) => { 
                    setTimeout(() => { // 保障将来onFulfilled在resolve中被调用时是一个异步函数
                        try{
                            const result = onRejected(reason)
                            resolve(result)
                        }catch(error){
                            reject(error)
                        }
                    })
                })
            }
        })
        return newPromise
    }

手写Promise.race

思路:

  1. 使用for...of方法遍历传入的 promises 数组(或者具有迭代器接口的数据),对每个 promise 调用 then 方法。这样也能判断数组元素是不是一个Promise对象。

  2. 由于 Promise.race 方法只需要取第一个变化状态的 promise 的结果,因此在任何一个 Promise 的状态变更后,都应该立即调用resolve 或 reject,而不是等待所有 promise 的状态都变化。

代码:

    static race (promises) { //不被实例对象访问,不是原型上的方法
        return new MyPromise((resolve,reject) => {
            // 看promises里面的哪个对象的状态先变更,谁先就拿谁的value或者reason
            for(let promise of promises){
                promise.then(
                    (value) => {
                        resolve(value)
                    },
                    (reason) => {
                        reject(reason)
                    }
                )
            }
        })
    }

手写Promise.all

思路:

  1. 使用普通的for循环遍历传入的 promises 数组(或者具有迭代器接口的数据),对每个 promise 调用 then 方法。使用 count 变量来记录已经成功处理的 promise 的数量,并使用 arr 数组来存储每个 promise 成功时的结果。

    为什么不用for..of?因为获得的成功结果的数组里面的数据顺序和接收到的数组顺序是一致的,所以需要存下每次循环的下标,防止被异步操作影响。

  2. count 变量的值等于传入的 promises 数组的长度时,说明所有 promise 都已经成功处理,此时调用 resolve 方法并传入 arr 数组作为结果。如果有任何一个 promise 的状态变为 rejected,则立即调用 reject 方法并传入该 promise 的失败结果。

代码:

    static all(promises){
        return new MyPromise((resolve,reject) =>{
            let count = 0, arr = []
            for( let i = 0; i< promises.length; i++) {
                promises[i].then(
                    (value) => {
                        count++
                        arr[i] = value
                        if(count === promises.length) {
                            resolve(arr)
                        }
                    },
                    (reason) =>{
                        reject(reason)
                    }
                )
            }
        })
    }

最后

恭喜你对Promise的理解更加深刻了!

文章参考:

阮一峰ES6入门-Promise