Promise原理讲解|手撕源码|实现实例和类方法

一、Promise源码一步一步手撕

本文章将通过promise的基本使用和一些promise的特性,来一步一步完成我们自己的promise。+代表新增代码

1. 创建promise,可以修改状态、保存值,状态只能修改一次

接收一个执行器回调函数在内部立即调用,该回调函数接收两个参数resolve,reject,可以改变promise对象的状态且保存值,所以我们需要实现这两个函数,且promise内部维护一个值result和一个状态state("pengding","fulfilled","rejected"),这个状态只能修改一次,在调用resolve()reject()都是无效的。

js 复制代码
const promise = new Promise((resolve, reject) => {
    resolve() //该promise为fulfilled
    reject() // 无效,该promise还是为fulfilled
})
const promise = new Promise((resolve, reject) => {
    reject() //该promise为rejected
})
const promise = new Promise((resolve, reject) => {
    throw new Error('抛出错误') //该promise为rejected
})
js 复制代码
class Promise{
    constructor(executor) {
        // 保存每个promise实例对象的状态 默认为pending
        this.state = 'pending'
        // 存储promise对象的数据
        this.result = null
        // 在其他地方调用resolve和reject函数,会导致this绑定到window
        const that = this
        // 改变promise实例对象状态为fulfilled
        function resolve(data) {
            // 保证只能修改一次状态 其他状态下无法再次修改
            if(that.state === 'pending') {
                that.state = 'fulfilled'
                that.result = data
             }
        }
        // 改变promise实例对象状态为rejected
        function reject(data) {
            // 保证只能修改一次
            if(that.state === 'pending'){
                that.state = 'rejected'
                that.result = data
            }
        }
        // 执行构造器回掉函数
        try {
            // 传入resolve和reject函数
            executor(resolve, reject)
        }catch (e){
            // 如果抛出异常则直接变为rejected状态
            reject(e)
        }
   }
}
2. 一个promise 指定的多个成功/失败回调函数都会调用

在一个promise对象上注册的多个成功/失败回调函数,在状态改变的时候,都会执行。

js 复制代码
const promise = new Promise((resolve, reject) => {})
promise.then(res=>{},reason=>{})
promise.then(res=>{},reason=>{})

then方法接收一个成功和一个失败回调函数,在状态改变时所有的回调函数都会执行,此时我们需要一个callbacks数组来保存全部的回调函数。

js 复制代码
class Promise{
    constructor(executor) {
        // 保存每个promise实例对象的状态 默认为pending
        this.state = 'pending'
        // 存储promise对象的数据
        this.result = null
+       // 保存每个promise实例对象的回掉函数 以对象{成功/失败}存储
+       this.callbacks = []
        // 在其他地方调用resolve和reject函数,会导致this绑定到window
        const that = this
        // 改变promise实例对象状态为fulfilled
        function resolve(data) {
            // 保证只能修改一次状态 其他状态下无法再次修改
            if(that.state === 'pending') {
                that.state = 'fulfilled'
                that.result = data
+               // 遍历执行所有成功回掉函数
+              that.callbacks.forEach(item => item.onResolved())
             }
        }
        // 改变promise实例对象状态为rejected
        function reject(data) {
            // 保证只能修改一次
            if(that.state === 'pending'){
                that.state = 'rejected'
                that.result = data
+               // 遍历执行所有失败回掉函数
+               that.callbacks.forEach(item => item.onRejected())
            }
        }
        // 执行构造器回掉函数
        executor(resolve, reject)
   }
   // then方法
+  then(onResolved, onRejected){
+      
+  }
}
3. 改变 promise 状态和指定回调函数先后顺序

同步改变: 在一个已有状态的promise对象上注册了回调函数,则对应状态的回调函数会立即进入微任务队列等待执行

异步改变: 在一个"pending"状态的promise对象上注册回调函数,该回调函数会保存起来,等待状态改变再进入微任务队列等待执行

现在来继续完善我们的then方法,暂时不需要constructor构造函数,所以直接写then方法

js 复制代码
class Promise{
    constructor(executor){}
    
    // then函数实现
    then(onResolved, onRejected){
        // 执行回掉函数 通过setTimeout模拟进入微任务队列
        const callback = (type) => {
            setTimeout(()=>{
                // 调用回调需要传result进去
                type(this.result)
            })
        }
        // 当前成功状态下直接调用then成功回掉 回掉函数直接进入微任务队列
        if(this.state === 'fulfilled') {
            callback(onResolved)
        // 当前失败状态下直接调用then失败回掉 回掉函数直接进入微任务队列
        }else if(this.state === 'rejected') {
            callback(onRejected)
            // 当前pending状态调用then 注册成功和失败回掉函数 等待状态改变对应回掉函数进入微任务队列
        }else{
            // 把成功/失败回掉函数加入callbacks数组
            this.callbacks.push({
                // 执行onResolved() 就会执行callback(onresolved)
                onResolved: () => {
                    callback(onResolved)
                },
                // 执行onResolved() 就会执行callback(onresolved)
                onRejected: () => {
                    callback(onRejected)
                }
            })
        }
    }
}
4. then()返回的promise的状态由回调函数执行结果决定,未执行回调函数时为"pending"状态

抛出异常: then()返回的promise状态变为"rejected",值为抛出的异常

返回非promise的任意值: then()返回的promise状态变为"fulfilled"值为回调函数返回的值

返回promise: then()返回的promise状态为回调函数返回的promise的状态,值为回调函数返回的promise的值

js 复制代码
// promise的状态为rejected
const promise = Promise.resolve().then(res=>{
    throw new Error('then回调中抛出错误')
})
// promise的状态为fulfilled
const promise = Promise.resolve().then(res=>{
    return 1
})
// promise的状态为返回promise状态:state=rejected,result='我是失败的promise'
const promise = Promise.resolve().then(res=>{
    return Promise.reject('我是失败的promise')
})

简单的使用之后,我们来完善我们then()方法中的逻辑,因为执行回调都是在callback函数中,所以我们只要关注callback函数如何写就可以,但是在这个前提下我们需要给then()方法返回一个promise对象

js 复制代码
class Promise{
    constructor(executor){}
    
    // then函数实现
    then(onResolved, onRejected){
+       return new Promise((resolve, reject)=>{
            // 执行回掉函数 通过setTimeout模拟进入微任务队列
            const callback = (type) => {
                setTimeout(()=>{
+                   try{
+                       let res = type(this.result)
+                       // 如果执行结果返回的是promise
+                       if(res instanceof Promise) {
+                           // 执行结果返回的promise状态决定then返回的promise状态
+                           res.then(v => {
+                           // 执行结果返回promise状态为fulfilled
+                               resolve(v)
+                           }, r => {
+                           // 执行结果返回promise状态为rejected
+                               reject(r)
+                           })
+                       // 如果执行结果返回的不是promise
+                       }else {
+                           resolve(res)
+                       }
+                    }catch (e){
+                       // 如果抛出错误则then返回的promise状态为rejected
+                       reject(e)
+                    }
                })
            }
            // 当前成功状态下直接调用then成功回掉 回掉函数直接进入微任务队列
            if(this.state === 'fulfilled') {
                callback(onResolved)
            // 当前失败状态下直接调用then失败回掉 回掉函数直接进入微任务队列
            }else if(this.state === 'rejected') {
                callback(onRejected)
                // 当前pending状态调用then 注册成功和失败回掉函数 等待状态改变对应回掉函数进入微任务队列
            }else{
                // 把成功/失败回掉函数加入callbacks数组
                this.callbacks.push({
                    // 执行onResolved() 就会执行callback(onresolved)
                    onResolved: () => {
                        callback(onResolved)
                    },
                    // 执行onResolved() 就会执行callback(onresolved)
                    onRejected: () => {
                        callback(onRejected)
                    }
                })
            }
+       })
    }
}
5. then()链式调用传递性:异常传透和值传递

失败传递: 未指定失败回调可以将失败值传递给后面的回调函数处理,在最后通过catch方法捕获

值传递: 未指定成功回调可以将成功值传递给后面的回调函数处理。

js 复制代码
Promise.resolve(data)
       .then()
       .then(1)
       // data被传递到第三个then中
       .then(data=>{throw new Error('中途出错啦')})
       .then(data=>{})
       // error直接被传递到最后的catch中
       .catch(err=>{console.log(err)})

这里补充一下,其实catch()方法很简单,就是调用的then()方法,只是第一个参数传入了undefined

js 复制代码
class Promise{
    // 构造函数
    constructor(exector){}
    // then方法
    then(onResolved, onRejected){}
    // catch方法
+   catch(onRejected){
+       this.then(undefined, onRejected)
+   }
}

继续完善我们的then()方法,then的第一个参数如果不是一个函数,则需要赋值一个函数可以将promise的成功状态传递下去,如果第二个参数不是一个函数,则需要赋值一个函数可以将promise的失败状态传递下去

js 复制代码
class Promise{
    constructor(executor){}
    
    // then函数实现
    then(onResolved, onRejected){
+       // (传递性)如果回掉函数为空,则需要传递成功值或者失败值
+       if(typeof onRejected !== 'function') {
+           onRejected = reason => throw reason
+       }
+       if(typeof onResolved !== 'function') {
+           onResolved = value => value
+       }
        return new Promise((resolve, reject)=>{
            // 执行回掉函数 通过setTimeout模拟进入微任务队列
            const callback = (type) => {
                setTimeout(()=>{
                    try{
                        let res = type(this.result)
                        // 如果执行结果返回的是promise
                        if(res instanceof Promise) {
                            // 执行结果返回的promise状态决定then返回的promise状态
                            res.then(v => {
                            // 执行结果返回promise状态为fulfilled
                                resolve(v)
                            }, r => {
                            // 执行结果返回promise状态为rejected
                                reject(r)
                            })
                        // 如果执行结果返回的不是promise
                        }else {
                            resolve(res)
                        }
                     }catch (e){
                        // 如果抛出错误则then返回的promise状态为rejected
                        reject(e)
                     }
                })
            }
            // 当前成功状态下直接调用then成功回掉 回掉函数直接进入微任务队列
            if(this.state === 'fulfilled') {
                callback(onResolved)
            // 当前失败状态下直接调用then失败回掉 回掉函数直接进入微任务队列
            }else if(this.state === 'rejected') {
                callback(onRejected)
                // 当前pending状态调用then 注册成功和失败回掉函数 等待状态改变对应回掉函数进入微任务队列
            }else{
                // 把成功/失败回掉函数加入callbacks数组
                this.callbacks.push({
                    // 执行onResolved() 就会执行callback(onresolved)
                    onResolved: () => {
                        callback(onResolved)
                    },
                    // 执行onResolved() 就会执行callback(onresolved)
                    onRejected: () => {
                        callback(onRejected)
                    }
                })
            }
        })
    }
    // catch方法
     catch(onRejected){}
}
6. Promise.resolve()静态类方法

通过该函数,可以对传入的data快速生成一个promise对象,如果传入的data为promise,则生成的promise对象状态和值都为data的状态和值,否则生成的promise的状态为"fulfilled"且值为data

js 复制代码
const promise = Promise.resolve(1) //state='fulfilled' result=1
const promise = Promise.resolve(new Error) // state='fulfilled' result=Error()
const promise = Promise.resolve(new Promise((resolve, reject) =>reject(1)))//state='rejected' result=1
js 复制代码
class Promise{
    // 构造函数
    constructor(executor){}
    // then方法
    then(onResolved, onRejected){}
    // catch方法
    catch(onRjected)
    // resolve类方法
    static resolve(data){
        return new Promise((resolve, reject) => {
            // 如果为promise对象
            if(data instaceof Promise){
                data.then(res=>resolve(res),err=>reject(err))
            // 不是promise对象
            }else{
                resolve(data)
            }
        })
    }
}
7. Promise.reject()静态类方法

通过该函数,可以对传入的data快速生成一个promise对象,状态为"rejected",值为data

js 复制代码
const promise = Promise.reject(1) //state='rejected' result=1
const promise = Promise.reject(new Error) // state='rejected' result=Error()
const promise = Promise.reject(new Promise((resolve, reject) =>resolve(1)))//state='rejected' result=Promise<state="fulfilled" result=1>
js 复制代码
class Promise{
    // 构造函数
    constructor(executor){}
    // then方法
    then(onResolved, onRejected){}
    // catch方法
    catch(onRjected)
    // resolve类方法
    static resolve(data){}
    // reject类方法
    static reject(data) {
        return new Promise((resolve, reject) => reject(data))
    }
}
8. Promise.all()静态类方法

传入一个promise数组,返回一个promise,该promise的状态由传入promise数组的状态来决定,如果该数组内全部promise的状态为"fulfilled"则该promise的状态为"fulfilled"且值为数组内全部promise的值的数组

js 复制代码
const promise = Promise.all([Promise.resolve(1), Promise.resolve(2)]) //state="fulfilled" result=[1,2]
const promise = Promise.all([Promise.resolve(1), Promise.reject(2)]) //state="rejected" result=2
js 复制代码
class Promise{
    // 构造函数
    constructor(executor){}
    // then方法
    then(onResolved, onRejected){}
    // catch方法
    catch(onRjected)
    // resolve类方法
    static resolve(data){}
    // reject类方法
    static reject(data) {}
    // all类方法
    static all(promises) {
        return new Promise((resolve, reject) => {
            let count = 0
            let result = []
            for(let i = 0; i < promises.length; i++) {
                // 遍历全部promise,注册回调函数
                promises[i].then(value => {
                    // 有成功就把值加入数组
                    result[i] = value
                    // 记录成功的数量
                    count++
                    // 如果全部都成功了
                    if(count === promises.length) {
                        // 改变promise状态为成功且值为数组result
                        resolve(result)
                    }
                }, reason => {
                    // 失败情况
                    reject(reason)
                })
            }
        })
    }
}
8. Promise.race()静态类方法

传入promise数组 返回一个promise,该promise的状态和值为传入的promise数组中第一个有状态的promise对应的状态和值

js 复制代码
const promise1 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve(1)
    },1000)
})
const promise1 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        reject(2)
    },2000)
})
const promise = Promise.race([promise1, promise2])//state='fulfilled' result=1
js 复制代码
class Promise{
    // 构造函数
    constructor(executor){}
    // then方法
    then(onResolved, onRejected){}
    // catch方法
    catch(onRjected)
    // resolve类方法
    static resolve(data){}
    // reject类方法
    static reject(data) {}
    // all类方法
    static all(promises) {}
    // race类方法
    static race(promises) {
        return new Promise((resolve, reject) => {
            for(let i = 0; i < promises.length; i++){
                // 全部promise注册回调函数
                promises[i].then(value => {
                    // 谁第一个执行回调就是谁的状态和值
                    resolve(value)
                }, reason => {
                    reject(reason)
                })
            }
         })
    }
}

二、总结

大家如果跟着一步一步实现到这里,相信对promise的底层实现,包括在面试中肯定都没有问题了。完整代码就不贴出来啦,如果上面步骤跟着写肯定已经有完整的版本了。

相关推荐
joan_8530 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖10 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
black^sugar11 小时前
纯前端实现更新检测
开发语言·前端·javascript
2401_8576009513 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_8576009513 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js