promise前端进阶必学

1,033 阅读14分钟

Promise

郑重声明,这篇文章虽然有手写promise的代码(引用), 但是文章重点不是手把手教你写一个promise!!!

这仅仅是一篇视频学习笔记,视频链接在此

看了网上各种各样的手写promise和promise教程,我必须承认这个是我看过讲的最好的promise教程!!!昨天一口气看到了凌晨两点,从使用到深入挖掘,再到最后的实现,真的超级精彩,本人语言表达能力有限,尽量给大家讲明白,强烈推荐大家去看原视频,看完你会有一种醍醐灌顶的感觉!!!

Promise是什么

promise是js异步编程新的解决方案(旧的是什么?)

  • 语法上: Promise是一个构造函数
  • 功能上: Promise对象用来封装一个异步操作并可以获得其结果

Promise状态:promise有三种状态

初始状态: pending 成功: fulfilled 失败: rejected

无论成功还是失败, 都会有一个结果(value or reason)

状态一旦从pending变为其他两个状态,状态不可以再变了,结果也不会变了

Promise的执行流程

                                                                                   then()                 
                                成功,执行resolve() > Promise对象(fulfilled状态) > 回调onFulfilled()                

  new Promise() > 执行异步操作 {                                                                   } 新的Promise对象            

                                失败,执行reject()  > Promise对象(rejected状态)  > 回调onRejected()           
                                                                                 then()/catch() 
                    

异步解决方案

promise为异步而生,在promise出现之前,我们的异步解决方案就是回调函数(回调函数(callback function)就是给另外一个宿主函数做参数的函数。回调函数在宿主函数内执行,执行结果返回给宿主函数)

现在有三件事doFirstThing, doSecondThing, doThirdThing, 第二件事依赖第一件事的结果,第三件事依赖第二件事的结果,如果我们要得到第三件事的结果

现在我们用不同的方案来实现

  1. 回调函数
doFirstThing(function(result) { //第一个函数function就是sucessCallback
  doSecondThing(result, function (newResult) {
    doThirdThing(newResult, function (finalResult) {
      console.log('Got the final result' + finalResult)
    }, failureCallback)
  }, failureCallback)
}, failureCallback)

一旦嵌套太深,代码将会难以阅读和维护, 也就是形成了传说中的回调地狱(callback hell)

  1. 用promise异步编程
doFirstThing().then(function(result) { //result是doFirstThing函数成功执行的返回值
        return doSecondThing(result) //执行器函数,同步回调
    })
    .then(function(newResult) { //newResult是doSecondThing成功执行的返回值
        return doThirdThing(newResult)
    })
    .then(function(finalResult) {
        console.log('Got the final result' + finalResult)
    })
    .catch(failureCallback) //统一的错误处理

回调函数采用了嵌套的方式依次调用doFirstThing(), doSecondThing(), doThirdThing(), 而Promise使用then将它们链接起来。

相比回调函数而言,Promise代码可读性更高,代码的执行顺序一目了然

JavaScript从ES8(即ECMAScript 2017)开始支持Async/Await。它让我们可以采用同步的方式调用Promise函数,提高异步代码的可读性。

  1. 异步的终极解决方案 aysnc/await

在定义函数时,在其前面添加一个async关键字,就可以在函数内使用await了。当await一个Promise时,代码会采用非阻塞的方式继续执行下去。当Promise成功resolve了,await语句会正真执行结束,并获取resolve的值。当Promise失败reject了,await语句初会throw一个错误。

async function request() {
    try {
        const result = await doFirstThing()
        const newResult = await doSecondThing(result)
        const finalResult = await doThirdThing(newResult)
        console.log('Got the final result' + finalResult)
    } catch (error) {
        failureCallback(error)
    }
}

忽然之间,代码的可读性提高了非常多!当然,async/await的神奇之处不止于此。async/await的出错处理非常方便,因为我们可以把同步代码和异步代码写在同一个try…catch…语句中。async/await代码调试更加方便,使用Promise时,我们无法设置断点,而async/await代码可以像同步代码一样设置断点!!

Promise API介绍

  1. Promise.resolve

    返回一个成功/失败的promise对象

  2. Promise.reject

    返回一个失败的promise对象

  3. Promise.all方法: (promises) => {}

    promises: 包含n个promise的数组 说明: 返回一个新的promise, 只有所有的promise都成功才成功

  4. Promise.race方法: (promises) => {}

    promises: 包含n个promise的数组 说明: 返回一个新的promise, 第一个完成的promise的结果状态就是最终的结果状态

搞懂几个Promise的关键问题

1 error属于promise哪个状态

const p = new Promise((resolve, reject) => {
    throw new Error('出错了') //属于rejected状态
})

p.then(
    value => {},
    reason => {
        console.log('reason', reason)
    }

)
//reason Error: 出错了

2 一个promise指定多个成功/失败回调函数

const p2 = new Promise((resolve, reject) => {
    throw new Error('出错了') //属于rejected状态
})

p2.then(
    value => {},
    reason1 => {
        console.log('reason1', reason1)
    }
).then(
    reason2 => {
        console.log('reason2', reason2)
    }
)
// 打印结果
// reason1 Error: 出错了
// reason2 undefined

3 状态改变与指定回调函数的先后次序

  1. 先指定回调函数,保存当前指定的回调函数,后改变状态(同时指定数据),异步执行回调函数
new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1) //后改变状态(同时指定数据),异步执行回调函数
    }, 1000)
}).then( //先指定回调函数,保存当前指定的回调函数
    value => {},
    reason => {
        console.log('reason', reason)
    }
)
  1. 先改变状态(同时指定数据), 后指定回调函数,异步执行回调函数
new Promise((resolve, reject) => {
    resolve(1) //先改变状态(同时指定数据)
}).then( //后指定回调函数,异步执行回调函数
    value => {
        console.log('value', value)
    },
    reason => {
        console.log('reason', reason)
    }
)
console.log('-----') //先输出----, 再输出value 1

4 promise.then()返回的新promise的结果状态由什么决定

我们之道,执行then是返回一个新的promise的,所以我们才可以链式调用,那么then返回的新的promise的结果和状态到底由什么决定?

看完下面几个例子,相信你可以自己总结出来

在此申明一下,这个问题很重要,提醒一遍,最后会来一个终极题目考察你对promise的掌握程度

4.1 在then里面什么也不返回

new Promise((resolve, reject) => {
    resolve(1)
}).then(
    value => {
        console.log("onFulfilled1()", value)
    }
).then(
    value => {
        console.log("onFulfilled2()", value)
    },
    reason => {
        console.log("onRejected2()", reason)
    }
)
// 打印结果
// onFulfilled1() 1
// onFulfilled2() undefined

4.2 在then里面返回一个promise

如果返回一个fulfilled的promise, 那么继续then调用会执行成功的回调,如果返回一个rejected的promise,那么继续then调用会执行失败的回调

new Promise((resolve, reject) => {
    resolve(1)
}).then(
    value => {
        console.log("onFulfilled1()", value)
        return Promise.resolve(3) //返回一个新的且状态为fulfilled的promise
    }
).then(
    value => {
        console.log("onFulfilled2()", value)
    },
    reason => {
        console.log("onRejected2()", reason)
    }
)
// 打印结果
// onFulfilled1() 1
// onFulfilled2() 3
new Promise((resolve, reject) => {
    resolve(1)
}).then(
    value => {
        console.log("onFulfilled1()", value)
        return Promise.reject(3) //返回一个新的且状态为rejected的promise
    }
).then(
    value => {
        console.log("onFulfilled2()", value)
    },
    reason => {
        console.log("onRejected2()", reason)
    }
)
// 打印结果
// onFulfilled1() 1
// onRejected2() 3

4.3 在then里面抛出一个异常

new Promise((resolve, reject) => {
    resolve(1)
}).then(
    value => {
        console.log("onFulfilled1()", value)
        throw 4 //新Promise状态为rejected, throw得到value值
    }
).then(
    value => {
        console.log("onFulfilled2()", value)
    },
    reason => {
        console.log("onRejected2()", reason)
    }
)
// 打印结果
// onFulfilled1() 1
// onRejected2() 4

4.4 在then里面返回一个非Promise, 非error的普通值

new Promise((resolve, reject) => {
    resolve(1)
}).then(
    value => {
        console.log("onFulfilled1()", value)
        return 2 //新Promise状态为fulfilled, return得到value值
    }
).then(
    value => {
        console.log("onFulfilled2()", value)
    },
    reason => {
        console.log("onRejected2()", reason)
    }
)
// 打印结果
// onFulfilled1() 1
// onFulfilled2() 2

现在你知道了吧?执行then返回的promise的状态和结果是由then中运行的回调函数执行后返回的结果决定的,(注意:then中运行的的回调函数可能是onFulfilled的回调,也可能是onRejected的回调)

如果还不理解这句话,对着上面几个例子,再好好体会下,我只能说成这样了

如何串联多个操作任务

new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('执行任务1(异步)')
        resolve(1)
    }, 1000)
}).then(
    value => {
        console.log('任务1的结果', value)
        console.log('执行任务2(同步)')
        return 2
    }
).then(
    value => {
        console.log('任务2的结果', value)

        return new Promise((resolve, reject) => {
            //启动任务3(异步)
            setTimeout(() => {
                console.log('执行任务3(异步)')
                resolve(3)
            }, 1000)
        })
    }
).then(
    value => {
        console.log('任务3的结果', value)
    }
)

异常穿透

我们平时在使用then的时候,一般只传入成功的回调,也就是只传入一个参数,那么为什么最后我们还可以捕获到呢,这是因为第二个参数是默认的抛出异常的回调,这样才可以实现穿透

new Promise((resolve, reject) => {
    //resolve(1)
    reject(1)
}).then(
    value => {
        console.log('onFulfilled1()', value)
        return 2
    },
    //reason => Promise.reject(reason)
    reason => {
        throw reason
    } //默认failureCallback
).then(
    value => {
        console.log('onFulfilled2()', value)
        return 3
    }
).then(
    value => {
        console.log('onFulfilled3()', value)
    }
).catch(reason => {
    console.log('onRejected1()', reason)
})

中断Promise链

返回一个pending的promise就可以

new Promise((resolve, reject) => {
    reject(1)
}).then(
    value => {
        console.log('onFulfilled1()', value)
        return 2
    }
).then(
    value => {
        console.log('onFulfilled2()', value)
        return 3
    }
).then(
    value => {
        console.log('onFulfilled3()', value)
    }
).catch(reason => {
    console.log('onRejected1()', reason)
    return new Promise(() => {}) //返回一个pending的promise 中断promise链
}).then(
    value => {
        console.log('onFulfilled4()', value)
    },
    reason => {
        console.log('onRejected4()', reason)
    }
)

来道终极考题

跳过开胃小菜,直接上大餐,考考你到底理解了Promise没有

setTimeout(() => {
    console.log(0)
}, 0)
new Promise((resolve, reject) => {
    console.log(1)
    resolve()
}).then(() => {
    console.log(2)
    new Promise((resolve, reject) => {
        console.log(3)
        resolve()
    }).then(() => {
        console.log(4)
    }).then(() => {
        console.log(5)
    })
}).then(() => {
    console.log(6)
})
new Promise((resolve, reject) => {
    console.log(7)
    resolve()
}).then(() => {
    console.log(8)
})

答案是1 7 2 3 8 4 6 5 0,光看是看不出来的,要分析的,4 6 5这里比较难,我简单分析下

有两点准则请注意:

  1. promise(...).then()的情况是执行promise,执行then(then的调用是同步的,只是里面的回调是异步的),then中的回调放入微任务队列,promise状态变为fulfilled(执行了resolve)或者rejected(执行了reject)了,then的回调才会执行

  2. 对于多个then链式调用的情况,注意,必须前一个then的回调执行完毕返回,后一个then的回调才会放入微任务队列。

分析:

第一轮:

0放入宏任务队列, 执行同步代码1, 2放入微队列, 执行同步代码7, 8放入微队列

输出: 1 7 

宏队列 [0]

微队列 [2, 8]

第二轮:取出宏任务对列的对头2,输出2, 继续往下执行,输出3, 4放入微任务队列,6也放入微任务队列, 5不会放入队列

这里简单分析下原因:执行3, 4 放入微任务队列,因为3已经执行完了,所以对于6来说,他的前一个then已经执行完了(没有显示return ,返回的是undefined) 所以6可以放入队列了,但是5不可以,必须4执行,5才可以放入队列

输出: 2 3

宏任务: [0]

微任务:[8 4 6]

第三轮 :执行8,输出8,执行4, 5放入队列

输出: 8 4 

宏任务: [0]

微任务:[6 5]

后面就不分析了 ,依次输出 6 5 0

所以最终的结果是1 7 2 3 8 4 6 5 0

如果还是不能理解的话,指路视频解析

一个简易的promise

/*
自定义Promise函数模块: IFFE
es5定义模块方法: 匿名函数(自)调用
*/

(function(window) {

    const PENDING = 'pending'
    const FULFILLED = 'fulfilled'
    const REJECTED = 'rejected'

    /*
    Promise构造函数
    excutor: 执行器函数(同步执行), 含2个参数(resolve, reject) => {}
    */
    function Promise(excutor) {
        const self = this //令函数resolve内的self与Promise的self一致

        self.status = PENDING //satus属性,初始为pending
        self.data = undefined //给promise对象指定一个用于存储结果数据的属性
        self.callbacks = [] //每个元素的结构: {onFulfilled(value){}, onRejected(reason){} }

        function resolve(value) {
            //如果当前状态不是pending,直接结束
            if (self.status !== PENDING) {
                return
            }

            //将状态改为resolved
            self.status = FULFILLED
            //保存value数据
            self.data = value
            //如果有待执行的callback函数,立刻异步执行回调函数
            if (self.callbacks.length > 0) {
                setTimeout(() => { //放入队列中执行所有成功的回调函数
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onFulfilled(value)
                    });
                })
            }
        }

        function reject(reason) {
            //如果当前状态不是pending,直接结束
            if (self.status !== PENDING) {
                return
            }

            //将状态改为rejected
            self.status = REJECTED
            //保存reason数据
            self.data = reason
            //如果有待执行的callback函数,立刻异步执行回调函数onRejected
            if (self.callbacks.length > 0) {
                setTimeout(() => { //放入队列中执行所有失败的回调函数
                    self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onRejected(reason)
                    });
                })
            }
        }

        //立即同步执行excutor
        try {
            excutor(resolve, reject)
        } catch (error) { //如果执行器抛出异常,Promise对象变为rejected状态
            reject(error)
        }

    }

    /*
    Promise原型对象的then()
    指定成功和失败的回调函数
    返回一个新的promise对象,结果由onFulfilled/onRejected的执行结果决定
    onFulfilled回调函数: value => {}
    onRejected回调函数: reason => {}
    */
    Promise.prototype.then = function(onFulfilled, onRejected) {

        onFulfilled = typeof onFulfilled === 'function' ?
            onFulfilled : value => value

        //指定默认是失败回调
        onRejected = typeof onRejected === 'function' ?
            onRejected : reason => {
                throw reason
            }

        const self = this

        //返回一个新的promise对象
        //(状态改变) 
        return new Promise((resolve, reject) => {

            /*
            调用指定的回调函数处理,根据执行的结果,改变return的promise状态
            callback回调函数: data => {}
            */
            function handle(callback) {
                /*

                  1. 如果抛出异常,return的promise就会失败,reason就是error
                  2. 如果回调函数返回不是promise, return的promise就会成功,value就是返回值
                  3. 如果回调函数返回的是promise, return的promise结果就是这个promise的结果

                  */
                try {
                    const result = callback(self.data)

                    if (result instanceof Promise) {
                        //3. 如果回调函数返回的是promise, return的promise结果就是这个promise的结果
                        // result.then(
                        //   value => resolve(value),  //当result成功,让return的promise也成功( x=>fn(x), 将fn(x)的返回值返回)
                        //   reason => reject(reason)  //当result失败,让return的promise也失败
                        // )
                        result.then(resolve, reject) //将result的结果当作新Promise的结果(这里的resolve/reject是控制新promise的回调函数)
                    } else {
                        //2. 如果回调函数返回不是promise, return的promise就会成功,value就是返回值
                        resolve(result)
                    }
                } catch (error) {
                    //1. 如果抛出异常,return的promise就会失败,reason就是error
                    reject(error)
                }
            }

            //当前状态为pending, 保存回调函数
            if (self.status === PENDING) { //这里的self不是新promise的this
                self.callbacks.push({
                    onFulfilled(value) {
                        handle(onFulfilled)
                    },
                    onRejected(reason) {
                        handle(onRejected)
                    }
                })
            } else if (self.status === FULFILLED) {
                setTimeout(() => {
                    handle(onFulfilled)
                })
            } else { // REJECTED
                setTimeout(() => {
                    handle(onRejected)
                })
            }
        })
    }

    /*
    Promise原型对象的catch()
    指定失败的回调函数
    返回一个新的promise对象
    */
    Promise.prototype.catch = function(onRejected) {
        return this.then(undefined, onRejected)
    }

    /*
    Promise函数对象的resolve方法
    返回一个指定结果的成功的promise
    */
    Promise.resolve = function(value) {
        //返回一个失败/成功的promise
        return new Promise((resolve, reject) => {
            //value是promise
            if (value instanceof Promise) { //使用value的结果作为promise的结果
                value.then(resolve, reject)
            } else { //value不是promise => promise成功,数据是value
                resolve(value)
            }
        })
    }

    /*
    Promise函数对象的reject方法
    返回一个指定reason的失败的promise
    */
    Promise.reject = function(reason) {
        return new Promise((resolve, reject) => {
            reject(reason)
        })
    }

    /*
    Promise函数对象的all方法
    返回一个promise,只有当所有promise成功时才成功
    */
    Promise.all = function(promises) {
        const values = new Array(promises.length) //用来保存所有成功value的数组
        //用来保存成功promise的数量
        let resolveCount = 0
        //返回新的promise
        return new Promise((resolve, reject) => {
            //遍历promises获取每个promise的结果
            promises.forEach((p, index) => {
                Promise.resolve(p).then( //p可以是数值
                    value => {
                        resolveCount++ //成功数量加1
                        //p成功,将成功的value保存进values
                        //values.push(value)  //push的value不能通过索引index获得
                        values[index] = value

                        // 如果全部成功,将return的promise改为成功
                        if (resolveCount === promises.length) {
                            resolve(values)
                        }
                    },
                    reason => { //只有一个失败,return的promise就失败
                        reject(reason) //无论reject还是resolve都只会执行一次,因为一个promise的结果一旦确定(resolved or rejected),就不可改
                    }
                )
            })
        })
    }

    /*
    Promise函数对象的race方法
    返回一个promise,其结果由第一个完成的promsie决定
    */
    Promise.race = function(promises) {
        //返回一个新的promise对象
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => { //forEach函数内部是异步的
                Promise.resolve(p).then(
                    value => {
                        resolve(value)
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })
    }
    window.Promise = Promise
})(window)

上面的实现,只是一个简易的promise, 距离promise A+规范差距还挺大的, promsise A+ 规范在链接传送门在此, 有兴趣有能力的小伙伴可以自己去研究。

引一个基本符合A+规范的Promise

这是前端面试之道作者写的一个promise, 这里拿来给大家参考,promiseA+规范有872个用例,我测了下,可以跑通846个,还有26个没有跑通,虽然没有跑通100%,但是相对网上各种promise的版本,通过率是我测试最高的,所以贴出来代码给大家参考。

const PENDING = 'pending'
const FUIFILLED = 'fulfilled'
const REJECTED = 'rejected'

function MyPromise(executor) {
    const self = this
    self.status = PENDING
    self.resolvedCallbacks = []
    self.rejectedCallbacks = []

    function resolve(value) {
        if (self.status === PENDING) {
            self.status = FUIFILLED
            self.value = value
            setTimeout(() => {
                self.resolvedCallbacks.forEach(fn => fn(self.value))
            })
        }
    }

    function reject(value) {
        if (self.status === PENDING) {
            self.status = REJECTED
            self.value = value
            setTimeout(() => {
                self.rejectedCallbacks.forEach(fn => fn(self.value))
            })
        }
    }

    try {
        executor(resolve, reject)
    } catch (error) {
        reject(error)
    }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : error => {
        throw error
    }
    let promise2
    if (self.status === PENDING) {
        promise2 = new MyPromise((resolve, reject) => {
            self.resolvedCallbacks.push(() => {
                try {
                    let x = onFulfilled(self.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
            self.rejectedCallbacks.push(() => {
                try {
                    let x = onRejected(self.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    }
    if (self.status === FUIFILLED) {
        promise2 = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    }
    if (self.status === REJECTED) {
        promise2 = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (err) {
                    reject(err)
                }
            })
        })
    }
    return promise2
}

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('same'))
    }
    let called = false
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, resolve, reject)
                }, e => {
                    if (called) return
                    called = true
                    reject(e)
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            reject(e)
        }

    } else {
        resolve(x)
    }
}

参考: