关于前端:从如何使用到如何实现一个Promise

44次阅读

共计 12724 个字符,预计需要花费 32 分钟才能阅读完成。

前言

这篇文章咱们一起来学习如何应用Promise,以及如何实现一个本人的Promise,解说十分分明,全程一步一步往后实现,附带具体正文与原理解说。

如果你觉的这篇文章有帮忙到你,❤️关注 + 点赞❤️激励一下作者,文章公众号首发,关注 前端南玖 第一工夫获取最新的文章,回复进群,拉你进入前端开发交换群,回复材料,获取海量前端电子书及前端学习视频~

promise 是什么?次要用来解决什么问题?

Promise 是异步编程的一种解决方案,比传统解决方案 – 回调函数和事件 – 更正当更弱小。

Promise特点:

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中),fulfilled(已胜利)和reject(已失败)。只有异步操作的后果,能够决定以后是哪一种状态,任何其它操作都无奈扭转这个状态。这也是Promise(承诺)这个名字的由来。

(2)一旦状态扭转,就不会再变,任何时候都能够失去这个后果。Promise对象的状态扭转,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为rejected

promise 次要用来解决:

  • 回调天堂
  • 并发申请
  • 异步计划优化(但它自身不是异步的,new Promise()后,它会立刻执行)

promise 根本用法

ES6 规定,Promise对象是一个构造函数,用来生成 Promise 实例

上面代码发明了一个 Promise 实例

const promise = new Promise(function(resolve,reject){
    //...
    if(/* 异步操作胜利 */){resolve(value)
    }else{
        // 异步操作失败
        reject(error)
    }
})

Promise构造函数承受一个函数作为参数,该函数的两个参数别离是 resolvereject. 他们是两个函数,由 JavaScript 引擎提供,不必本人部署。

resolve函数的作用是,将 Promise 对象的状态从“未实现”变成“胜利”(即从 pending 变为 resolve),在异步操作胜利时调用,并将异步操作的后果,作为参数传递进来;reject函数的作用是,将 Promise 对象的状态从“未实现”变成“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的谬误,作为参数传递进来。

Promise实例生成当前,能够用 then 办法别离指定 resolved 状态和 rejected 状态的回调函数, 或用 catch 办法指定 rejected 状态的回调函数。

promise.then(res=>{//success},error=>{//error}).catch(err=>{})

then办法能够承受两个回调函数作为参数,第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中,第二个函数是可选的,不肯定要提供。这两个函数都接管 Promise 对象传出的值作为参数。

Ok, 通过上面对 promise 根本用法的形容,咱们大略晓得了一个 promise 类外面都应该蕴含哪些内容了:

  • promise 状态:pending,fulfilled,rejected
  • promise 返回值
  • 执行器:promise 执行入口(也就是你传入的那个函数)
  • resolve:扭转 promise 状态为 fulfilled
  • reject:扭转 promise 状态为 rejected
  • then:接管两个回调,onFulfilled, onRejected。别离在 promise 状态变为 fulfiled 或 rejected 后执行
  • catch:承受一个回调,在 promise 状态变为 rejected 后执行

简略实现一个 promise

咱们晓得了一个 promise 内容至多蕴含以上那些内容,所以一个简略的 promise 外部至多是这样的

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise 状态
        this.promiseRes = null  // promise 返回值
           const resolve = result=>{//...}
        const reject = result=>{//...}
        try{init(resolve,reject)  // init 就是初始化执行器
        }catch(err){reject(err)
        }
        
    }
    then(onFulfilled,onRejected){//...}
      catch(onRejected){//...}
}

OK,大略理解之后,咱们再来一个一个的看外面每个局部的实现以及作用

Promise 的执行器

它其实是咱们在 new Promise 时传入的一个回调函数,这个函数自身是同步的,也就是说在 new Promise 时它就会执行,这也是咱们操作 promise 的入口。

class myPromise{
  //...
  constructor(init){
        try{init(resolve,reject)  // init 就是初始化执行器
        }catch(err){reject(err) // 这里次要是在 init 执行器函数出错时,用以让 promise 状态变为 rejected
        } 
    }
  //...
}

该函数承受两个回调函数(resolve,reject)作为参数,用以扭转 Promise 的状态

resolve 与 reject 办法

这两个函数作为参数传到执行器函数中,用以后续扭转 Promise 状态

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise 状态
        this.promiseRes = null  // promise 返回值
        this.resolveCallback = [] // 胜利回调汇合
        this.rejectCallback = [] // 失败回调汇合
        const resolve = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用胜利回调
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用失败回调
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{init(resolve,reject)  // 留神 this 指向
        }catch(err){reject(err)
        }
        
    }
}

初步 then 办法

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise 状态
        this.promiseRes = null  // promise 返回值
        this.resolveCallback = [] // 胜利回调汇合
        this.rejectCallback = [] // 失败回调汇合
        const resolve = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用胜利回调
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用失败回调
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{init(resolve,reject)  // 留神 this 指向
        }catch(err){reject(err)
        }
        
    }
    then(onFulfilled,onRejected){if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {onFulfilled(this.promiseRes)
        }
        if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {onRejected(this.promiseRes)
        }
    }
}

写到这里,咱们的 promise 曾经初步成型了,咱们能够来测试一下:

const res1 = new myPromise((res,rej)=>{res('胜利啦~')
    rej('失败啦~')
})
res1.then((res)=>{console.log(res)
},err=>{console.log(err)
})
// 依照预期,这里应该是只会打印出胜利啦~


从上图看咱们,是不是合乎咱们的预期,并且 myPromise 外部与原生的 Promise 也是十分类似的。你们是不是感觉这里曾经没问题了,下面咱们只是测了一下同步办法的执行,但别忘了,Promise 次要是来解决异步问题的,咱们再来试一下外面执行异步办法还符不合乎咱们的预期?

const res1 = new myPromise((res,rej)=>{setTimeout(()=>res('胜利啦~'),1000)
    // rej('失败啦~')
})
res1.then((res)=>{console.log(res)
})

这里咱们预期原本是一秒之后打印胜利啦,但它并没有如咱们所愿,反而是什么也没打印进去,这是因为在 setTimeout 执行之前(pen ding)这个 then 办法曾经执行过了,1s 后状态变成 fulfilled 时,then 也不会再执行了。

所以咱们须要保障 then 办法的回调函数在 promise 状态变成 fulfilledrejected时再执行, 那么当 promise 状态为 pending 时咱们先要把回调存在对应的队列中,等后续状态扭转后再执行

较完整 then 办法

OK,这里咱们批改一下咱们的 then 办法,让其保障异步代码执行的正确性 (具体实现微工作咱们能够用 mutationObserver,这里咱们就用 setTimeout 来模仿一下)

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise 状态
        this.promiseRes = null  // promise 返回值
        this.resolveCallback = [] // 胜利回调汇合
        this.rejectCallback = [] // 失败回调汇合
        const resolve = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用胜利回调
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用失败回调
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{init(resolve,reject)  // 留神 this 指向
        }catch(err){reject(err)
        }
        
    }
    then(onFulfilled,onRejected){if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {onFulfilled(this.promiseRes)
        }
        if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {onRejected(this.promiseRes)
        }
        if(this.state === myPromise.PENDING){if(onFulfilled && typeof onFulfilled === 'function'){this.resolveCallback.push(()=>
                // 这里咱们用 setTimeout 来模仿实现 then 的微工作
                setTimeout(()=>{onFulfilled(this.promiseRes)
                },0)
                )
            }
            if(onRejected && typeof onRejected === 'function'){this.rejectCallback.push(()=>
                // 这里咱们用 setTimeout 来模仿实现 then 的微工作
                setTimeout(()=>{onRejected(this.promiseRes)
                },0)
                )
            }
        }
    }
}

这里咱们能够再测试一下下面那个异步函数的测试用例,发现它可能正确打印,OK,一个较完整的 then 办法就算实现了~

then 的链式调用

then办法会返回一个新的Promise(⚠️留神:不是原来的那个Promise)所以能够采纳链式调用

采纳链式的 then,能够指定一组依照秩序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise 对象(即有异步操作),这时后一个回调函数,就会期待该 Promise 对象的状态发生变化,才会被调用。

then(onFulfilled,onRejected){const {promiseRes,state} = this
        let promise = new myPromise((reso,reje)=>{
            const resolveMyPromise = promiseRes => {
                try{if(typeof onFulfilled !== 'function'){
                        // 如果 then 的第一个回调不是一个函数,间接疏忽,返回一个新的 promise
                        reso(promiseRes)
                    }else{
                        // 获取第一个回调的执行后果
                        const res = onFulfilled(promiseRes)
                        // 看该执行后果是否是一个 promise
                        if(res instanceof myPromise){
                            // 是一个 promise, 等它状态扭转后再扭转 then 返回的 promise 状态
                            res.then(reso,rej) 
                        }else{
                            // 不是一个 promise,将它作为新的 promise 的 resolve
                            reso(res)
                        }
                    }
                }catch(err){
                    // 异样,间接将新的 promise 状态置为 rejected
                    reje(err)
                }
            }
            const rejectMyPromise = promiseRes => {
                try{if(typeof onRejected !== 'function'){
                        // 如果 then 的第二个回调不是一个函数,间接疏忽,返回一个新的 promise
                        reje(promiseRes)
                    }else{
                        // 获取第二个回调的执行后果
                        const res = onRejected(promiseRes)
                        // 看该执行后果是否是一个 promise
                        if(res instanceof myPromise){
                            // 是一个 promise, 等它状态扭转后再扭转 then 返回的 promise 状态
                            res.then(reso,rej) 
                        }else{
                            // 不是一个 promise,将它作为新的 promise 的 resolve
                            reje(res)
                        }
                    }
                    
                }catch(err){
                    // 异样,间接将新的 promise 状态置为 rejected
                    reje(err)
                }
            }
            if(state === myPromise.FULFILLEd) {resolveMyPromise(promiseRes)
            }
            if(state === myPromise.REJECTED) {rejectMyPromise(promiseRes)
            }
            if(state === myPromise.PENDING){if(onFulfilled && typeof onFulfilled === 'function'){this.resolveCallback.push(()=>
                    // 这里咱们用 setTimeout 来模仿实现 then 的微工作
                    setTimeout(()=>{resolveMyPromise(this.promiseRes)
                    },0)
                    )
                }
                if(onRejected && typeof onRejected === 'function'){this.rejectCallback.push(()=>
                    // 这里咱们用 setTimeout 来模仿实现 then 的微工作
                    setTimeout(()=>{rejectMyPromise(this.promiseRes)
                    },0)
                    )
                }
            }

        })
        return promise
    }

catch 办法

咱们晓得 then 的第二个回调其实与 catch 办法是一样的,所以 catch 办法咱们能够这样实现

catch(onRejected) {return this.then(undefined,onRejected)
    }

Promise.resolve

将对象转为一个 promise 对象,依据参数不通可分为四种状况

  • 参数是一个 Promise 实例,间接返回该实例
  • 参数是一个 thenable 对象,将该对象转为 Promise 对象后,执行该对象的 then 办法
  • 没有参数,也是返回一个状态为 resolved 的新的 Promise 对象
  • 参数是一个一个原始值,返回一个新的 Promise 对象,状态为resolved

手动实现:

static resolve(v){
  //1. 参数是一个 Promise 实例, 间接返回
  if(v instanceof myPromise){return v}
  //2. 参数是一个 thenable 对象,转为 Promise 后执行该对象的 then 办法
  if(typeof v === 'object' && typeof v.then === 'function'){return new myPromise((res,rej)=>{v.then(res,rej)
    })
  }
  //3. 没有参数,间接返回一个 resolved 状态的 promise
  if(!v){
    return new myPromise(res=>{res()
    })
  }
  //4. 参数是一个原始值,返回一个新的 Promise,状态为 resolved
  return new myPromise(res=>{res(v)
  })
}

Promise.reject

返回一个新的 Promise 对象,状态为rejected

static reject(v){return new myPromise((res,rej)=>{rej(v)
  })
}

Promise.all

该办法用于将多个 Promise 实例包装成一个新的 Promise 实例, 如果有不是 Promise 的项,则让该项间接胜利

用法:

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

p的状态由 p1p2p3 决定,分成两种状况。

(1)只有 p1p2p3 的状态都变成 fulfilledp 的状态才会变成 fulfilled,此时p1p2p3 的返回值组成一个数组,传递给 p 的回调函数。

(2)只有 p1p2p3 之中有一个被 rejectedp 的状态就变成 rejected,此时第一个被reject 的实例的返回值,会传递给 p 的回调函数。

Ok, 理解完 Promise.all 咱们入手来实现一遍

手动实现:

static all (promises){return new myPromise((res,rej)=>{
            let count = 0
            const result = [];
            function addFun(index,resf) {result[index]=resf // 这里用索引别用 push, 保障返回的程序
                count++
                if(count==promises.length) {res(result)
                }
            }
            [].forEach.call(promises,(promise,index)=>{if(promise instanceof myPromise) {
                    promise.then(success=>{
                        // count ++
                        // result.push(success)
                        addFun(index,success)
                    },err=>{rej(err)
                    })
                }else{addFun(index,promise)
                }
            })
        })
    }

Promise.race

Promise.race()办法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

用法:

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

下面代码中,只有 p1p2p3 之中有一个实例率先扭转状态,p的状态就跟着扭转。那个率先扭转的 Promise 实例的返回值,就传递给 p 的回调函数。

手动实现:

static race(promises) {return new myPromise((res,rej)=>{[].forEach.call(promises,promise=>{if(promise instanceof myPromise){
                    promise.then(success=>{res(success)
                    },error=>{rej(error)
                    })
                }else{res(promise)
                } 
            })
        })
    }

残缺代码

class myPromise {
    static PENDING = 'pending'
    static FULFILLEd = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(init){
        this.state = myPromise.PENDING // promise 状态
        this.promiseRes = null  // promise 返回值
        this.resolveCallback = [] // 胜利回调汇合
        this.rejectCallback = [] // 失败回调汇合
        const resolve = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.FULFILLEd // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用胜利回调
                this.resolveCallback.forEach(fn=>fn())
            }
        }
        const reject = result=>{
            // 只有当状态为 pending 时才扭转,保障状态一旦扭转就不会再变
            if(this.state === myPromise.PENDING){
                this.state = myPromise.REJECTED // 扭转状态
                this.promiseRes = result // 返回值
                // 顺次调用失败回调
                this.rejectCallback.forEach(fn=>fn())
            }
        }
        try{init(resolve,reject)  // 留神 this 指向
        }catch(err){reject(err)
        }
        
    }
    then(onFulfilled,onRejected){const {promiseRes,state} = this
        let promise = new myPromise((reso,reje)=>{
            const resolveMyPromise = promiseRes => {
                try{if(typeof onFulfilled !== 'function'){
                        // 如果 then 的第一个回调不是一个函数,间接疏忽,返回一个新的 promise
                        reso(promiseRes)
                    }else{
                        // 获取第一个回调的执行后果
                        const res = onFulfilled(promiseRes)
                        // 看该执行后果是否是一个 promise
                        if(res instanceof myPromise){
                            // 是一个 promise, 等它状态扭转后再扭转 then 返回的 promise 状态
                            res.then(reso,rej) 
                        }else{
                            // 不是一个 promise,将它作为新的 promise 的 resolve
                            reso(res)
                        }
                    }
                }catch(err){
                    // 异样,间接将新的 promise 状态置为 rejected
                    reje(err)
                }
            }
            const rejectMyPromise = promiseRes => {
                try{if(typeof onRejected !== 'function'){
                        // 如果 then 的第二个回调不是一个函数,间接疏忽,返回一个新的 promise
                        reje(promiseRes)
                    }else{
                        // 获取第二个回调的执行后果
                        const res = onRejected(promiseRes)
                        // 看该执行后果是否是一个 promise
                        if(res instanceof myPromise){
                            // 是一个 promise, 等它状态扭转后再扭转 then 返回的 promise 状态
                            res.then(reso,rej) 
                        }else{
                            // 不是一个 promise,将它作为新的 promise 的 resolve
                            reje(res)
                        }
                    }
                    
                }catch(err){
                    // 异样,间接将新的 promise 状态置为 rejected
                    reje(err)
                }
            }
            if(state === myPromise.FULFILLEd) {resolveMyPromise(promiseRes)
            }
            if(state === myPromise.REJECTED) {rejectMyPromise(promiseRes)
            }
            if(state === myPromise.PENDING){if(onFulfilled && typeof onFulfilled === 'function'){this.resolveCallback.push(()=>
                    // 这里咱们用 setTimeout 来模仿实现 then 的微工作
                    setTimeout(()=>{resolveMyPromise(this.promiseRes)
                    },0)
                    )
                }
                if(onRejected && typeof onRejected === 'function'){this.rejectCallback.push(()=>
                    // 这里咱们用 setTimeout 来模仿实现 then 的微工作
                    setTimeout(()=>{rejectMyPromise(this.promiseRes)
                    },0)
                    )
                }
            }

        })
        return promise
    }
    catch(onRejected) {return this.then(undefined,onRejected)
    }
    static all (promises){return new myPromise((res,rej)=>{
            let count = 0
            const result = [];
            function addFun(index,resf) {result[index]=resf // 这里用索引别用 push, 保障返回的程序
                count++
                if(count==promises.length) {res(result)
                }
            }
            [].forEach.call(promises,(promise,index)=>{if(promise instanceof myPromise) {
                    promise.then(success=>{addFun(index,success)
                    },err=>{rej(err)
                    })
                }else{addFun(index,promise)
                }
            })
        })
    }
    static race(promises) {return new myPromise((res,rej)=>{[].forEach.call(promises,promise=>{if(promise instanceof myPromise){
                    promise.then(success=>{res(success)
                    },error=>{rej(error)
                    })
                }else{res(promise)
                } 
            })
        })
    }
    static resolve(v){
        //1. 参数是一个 Promise 实例, 间接返回
        if(v instanceof myPromise){return v}
        //2. 参数是一个 thenable 对象,转为 Promise 后执行该对象的 then 办法
        if(typeof v === 'object' && typeof v.then === 'function'){return new myPromise((res,rej)=>{v.then(res,rej)
            })
        }
        //3. 没有参数,间接返回一个 resolved 状态的 promise
        if(!v){
            return new myPromise(res=>{res()
            })
        }
        //4. 参数是一个原始值,返回一个新的 Promise,状态为 resolved
        return new myPromise(res=>{res(v)
        })
    }
    static reject(v){return new myPromise((res,rej)=>{rej(v)
        })
    }
}

举荐浏览

介绍回流与重绘(Reflow & Repaint),以及如何进行优化?
这些浏览器面试题,看看你能答复几个?
这一次带你彻底理解前端本地存储
面试官:说一说前端路由与后端路由的区别
JavaScript 之原型与原型链
Javascript 深刻之作用域与闭包
this 指向与 call,apply,bind

总结

OK,下面跟大家一起过了一遍 Promise 的用法以及本人入手实现了一遍 Promise,想必看完这篇文章,大家对Promise 会有一个更加清晰的意识。

我是 南玖,感激各位的:「点赞和关注」,咱们下期见!

正文完
 0