手写Promise(前端面试题)第一篇 ·基本功能实现

目录

1.构造函数

2.状态与结果

3.then方法

4.异步任务

5.链式调用

小结


1.构造函数

promise构造函数接收一个回调函数作为参数,同时为该回调函数注入两个参数并立即调用该回调函数,传入参数分别是resolve,reject。这两个参数也是两个函数,调用之后就会对应的修改promise的状态与结果。

class MyPromise{

    constructor(callback) {
        // 为回调函数传入resolve和reject方法
        const resolve = (result)  => {

        }
        const reject = (result) =>  {

        }
        callback(resolve, reject)
    }
}

2.状态与结果

promise构造函数中为回调函数所注入的两个参数可以修改promise状态,并将其所携带的参数作为结果保存下来。因为promise的状态改变是不可逆的,因此在修改状态前需要判断此时状态是否为pending,当状态为pending时才会执行修改状态逻辑。

const resolve = (result)  => {
 // 状态变为fufilled
 if(this.state === "pending") {
   this.state = "fufilled"
   this.result = result
  }
}
const reject = (result) =>  {
 // 状态变为rejected
 if(this.state === "pending") {
  this.state = "rejected"
  this.result = result
 }
}

3.then方法

(1) .成功失败的回调

promise实例的then方法中接收两个参数,这两个参数均是回调函数,分别被命名为onFufilled,onRejected。这两个函数在promise的状态更改为对应的值时触发执行,并且会为其注入promise的结果作为参数。当调用then方法传入的参数不是函数时,会按照对应的处理逻辑将其包装成函数用以执行后续逻辑。

传入参数不是函数时的处理参考MDN文档:Promise.prototype.then() - JavaScript | MDN (mozilla.org)

then(onFufilled, onRejected) {
        // 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
        if(typeof onFufilled !== "function") {
            onFufilled = (x) => x
        }
        if(typeof onRejected !== "function") {
            onRejected = (x) => { throw(x) }
        }
        // 执行对应的回调
        if(this.state === "fufilled") {
            onFufilled(this.result)
        } else if(this.state === "rejected") {
            onRejected(this.result)
        }
    }

(2).异步与多次调用

当前then方法还存在一个问题,如果在promise的构造函数中执行了异步操作,比如定义一个定时器setTimeout,待到一秒钟之后再resolve抛出结果,此时then方法就无法接收到结果了。因此,我们需要在then方法中判断构造函数中是否为异步操作,如果是异步操作,则then方法调用时promise的状态一定还未改变。此时我们需要将回调函数是同一个执行队列保存起来,待到异步操作完成,再来执行对应的回调。这里我们使用一个对象数组来存储,因为对于promise对象实例来说,then方法是一个反复调用的(这里说的并非是链式调用,关于链式调用后续会解决),因此回调队列有可能不止一组的回调函数,所有用数组保存。(这里为了防止类外访问该回调队列,使用私有变量)

// then方法传入的回调函数队列
    #handlers = [] // [{onFufilled, onRejected}...]
then(onFufilled, onRejected) {
        // 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
        if(typeof onFufilled !== "function") {
            onFufilled = (x) => x
        }
        if(typeof onRejected !== "function") {
            onRejected = (x) => { throw(x) }
        }
        // 执行对应的回调
        if(this.state === "fufilled") {
            onFufilled(this.result)
        } else if(this.state === "rejected") {
            onRejected(this.result)
        } else if(this.state === "pending") {
            // 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
            this.#handlers.push({
                onFufilled,
                onRejected
            })
        }
    }

上面代码已经将异步操作时需要执行的回调函数保存到队列当中,接下来需要在构造函数中的resolve,reject两个对调函数执行时判断回调队列是否有函数,如有,则挨个执行对应的回调函数。 

const resolve = (result)  => {
            // 状态变为resolved
      if(this.state === "pending") {
            this.state = "fufilled"
            this.result = result
                // 执行回调队列
            this.#handlers.forEach((item) => {
               item.onFufilled(this.result)
            })
       }
}
const reject = (result) =>  {
      // 状态变为rejected
       if(this.state === "pending") {
            this.state = "rejected"
            this.result = result
            // 执行回调队列
            this.#handlers.forEach((item) => {
               item.onRejected(this.result)
             })
       }
 }

4.异步任务

(1).核心API

原生的promise执行后本质上是一个异步任务,然而我们现在写的MyPromise还不支持异步任务。让我们来看一个例子:

console.log(1);
const p = new MyPromise((resolve, reject) => {
    resolve(2)
})
p.then((res) => {
    console.log(res)
})

console.log(3);

这段代码实际会输出 1,2,3,但我们期望的事是promise的执行是一个异步过程。因此我们需要在MyPromise类内部执行对应的回调时将其添加到异步队列来执行。这里就涉及到了如何将回调添加到异步队列。我们这里主要使用三种API来实现。

1.queueMicrotask: 这是一个全局函数,现代浏览器与node均支持该API,它主要是主动将一个任务推送到微任务队列等待调用。使用方式:

queueMicrotask(() => {
    // 需要执行的异步任务
})

详情可见官网: queueMicrotask() - Web API | MDN (mozilla.org)

2. MutationObserver: 这是一个内置对象,用于创建一个观察器,可以通过观察器来观察dom元素的变化,用以执行相应的逻辑。观察到dom元素的变化后会将对应的执行逻辑推送到异步队列中等待调用。使用方式:

const obv = new MutationObserver(() => {
    // 执行对应的逻辑
})
const div = document.createElement("div")
obv.observe(div, { attributes: true })
div.setAttribute("name", "123")

详情可见官网: MutationObserver - Web API | MDN (mozilla.org)

3.setTimeout : 由于浏览器等各方面的限制,如果以上两个api未能实现异步,则可使用setTimeout来实现异步操作。(所有浏览器及node全兼容)

(2).功能封装

这里我们封转一个私有方法,在then方法中需要调用回调的时候,我们使用该方法将回调函数推入异步队列等待调用,这样一来就可以实现异步任务了。这里使用了三种实现,主要是考虑到兼容问题。

#runAsyncTask(callback) {
        if(typeof queueMicrotask === 'function') {
            // 1.queueMicrotask
            queueMicrotask(callback)
        } else if( typeof MutationObserver === 'function' ) {
            // 2.MutationObserver
            const obv = new MutationObserver(callback)
            const div = document.createElement("div")
            obv.observe(div, { attributes: true })
            div.setAttribute("name", "123")
        } else  {
            // 3.setTimeout
            setTimeout(callback, 0)
        }
    }

then方法改动:

 // 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
        if(typeof onFufilled !== "function") {
            onFufilled = (x) => x
        }
        if(typeof onRejected !== "function") {
            onRejected = (x) => { throw(x) }
        }
        // 执行对应的回调
        if(this.state === "fufilled") {
            // 执行异步任务
            this.#runAsyncTask(() => {
                onFufilled(this.result)
            })
        } else if(this.state === "rejected") {
            // 执行异步任务
            this.#runAsyncTask(() => {
                onRejected(this.result)
            })
        } else if(this.state === "pending") {
            // 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
            this.#handlers.push({
                onFufilled: () => {
                    this.#runAsyncTask(() => {
                        onFufilled(this.result)
                    })
                },
                onRejected: () => {
                    this.#runAsyncTask(() => {
                        onRejected(this.result)
                    })
                }
            })
        }
    }

 在为对调队列添加任务的时候,我们将onFufilled与onRejected也是用this.#runAsyncTask函数包装,待到真正调用的时候,就可以将其推入到异步队列当中。

5.链式调用

(1).成功状态

promise的then方法有个非常特殊的地方,这个方法可以链式的连续调用,本质上是由于then方法自身在执行完之后就会返回一个promise实例,实例上同样有then方法,这样就实现了连续调用。因此,在以上代码的基础上,我们需要在then方法中手动创建一个promise实例,最终将其返回。

同时,我们需要将此次then方法中onFufilled回调函数的返回结果作为参数传给新创建的promise的resolve函数中,如果上一次then方法中执行的逻辑报错,我们也需要将错误捕获,并通过reject函数传入新创建的promise中。

代码如下:

then(onFufilled, onRejected) {
        // 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
        if(typeof onFufilled !== "function") {
            onFufilled = (x) => x
        }
        if(typeof onRejected !== "function") {
            onRejected = (x) => { throw(x) }
        }
        // 创建一个用于返回的promise实例
        const newP = new MyPromise((resolve,reject) => {
            // 执行对应的回调
            if(this.state === "fufilled") {
                // 执行异步任务
                this.#runAsyncTask(() => {
                    // 捕获错误
                    try {
                        // 接收成功回调的返回值,作为下一个then的参数
                        const res = onFufilled(this.result)
                        resolve(res)
                    }
                    catch(e) {
                        reject(e)
                    }
                })
            } else if(this.state === "rejected") {
                // 执行异步任务
                this.#runAsyncTask(() => {
                    onRejected(this.result)
                })
            } else if(this.state === "pending") {
                // 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
                this.#handlers.push({
                    onFufilled: () => {
                        this.#runAsyncTask(() => {
                            onFufilled(this.result)
                        })
                    },
                    onRejected: () => {
                        this.#runAsyncTask(() => {
                            onRejected(this.result)
                        })
                    }
                })
            }
        })
        // 方法结束返回新的promise实例
        return newP
    }

当上一个then方法中返回一个正常的结果时,上面的代码运行正常。但当返回一个promise对象时,以上的方法会直接将promise传入下一个then方法中,这不是我们所期望的,我们期望的是将promise的结果传入下一个then方法,因此我们需要在拿到onFulfilled返回值之后来判断是否是一个promise对象,如果是,则需要单独判断。

// 接收成功回调的返回值,作为下一个then的参数
const res = onFufilled(this.result)
// 如果返回值时promise实例,则需要单独处理
if(res instanceof MyPromise) {
    res.then(resolve, reject)
} else {
    resolve(res)
}

 至此,then方法成功状态的链式调用完成,如下案例也可正常输出:2,3

const p = new MyPromise((resolve, reject) => {
    resolve(2)
})
p.then((res) => {
    console.log(res)
    return new MyPromise((resolve, reject) => {
        resolve(3)
    })
},err => {
    console.log(err)
}).then((data) => {
    console.log(data)
},err=> {
    console.log(err)
})

这里还存在一个小问题,原生的promise中,如果在then方法的onFufilled方法中返回了这个then方法 本身的返回值,js引擎就会报错,错误信息是:TypeError:Chaining cycle detected for promise #<Promise>,如下案例:

const p = new MyPromise((resolve, reject) => {
    resolve(2)
})
const p2 = p.then((res) => {
    return p2
},err => {
    console.log(err)
})

要实现这一功能,我们就需要在获取到onFufilled的结果后与then方法将要返回的结果进行比较,如果两者相等,我们就需要手动抛出一个错误。

代码如下:


const res = onFufilled(this.result)
// 判断是否重复引用,如果重复引用则抛出错误
if(res === newP) {
    throw new TypeError("Chaining cycle detected for promise #<Promise>")
}

(2).失败状态 

then方法的onRejected回调里面的操作和onFufilled基本类似,因此我们将上面所写的代码封装成一个私有方法,直接复用即可。

#resolvePromise(promise,res,resolve,reject) {
        // 判断是否重复引用,如果重复引用则抛出错误
        if(res === promise) {
            throw new TypeError("Chaining cycle detected for promise #<Promise>")
        }
        // 如果返回值时promise实例,则需要单独处理
        if(res instanceof MyPromise) {
            res.then(resolve, reject)
        } else {
            resolve(res)
        }
    }

 else if(this.state === "rejected") {
    // 执行异步任务
    this.#runAsyncTask(() => {
       try{
          const res = onRejected(this.result)
          this.#resolvePromise(newP, res, resolve, reject)
       } catch(err) {
            reject(err)
       }
    })
}

(3).默认状态 

由于默认状态下我们需要将回调函数添加到任务队列当中,等到真正状态改变时在执行,所以我们需要在添加任务时同样做以上的处理,处理代码与成功失败状态完全一致,直接使用封装的私有方法。

 else if(this.state === "pending") {
                // 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
                this.#handlers.push({
                    onFufilled: () => {
                        this.#runAsyncTask(() => {
                            try {
                                const res = onFufilled(this.result)
                                this.#resolvePromise(newP, res, resolve, reject)
                            }catch(err) {
                                reject(err)
                            }
                        })
                    },
                    onRejected: () => {
                        this.#runAsyncTask(() => {
                            try {
                                const res = onRejected(this.result)
                                this.#resolvePromise(newP, res, resolve, reject)
                            } catch(err) {
                                reject(err)
                            }
                        })
                    }
                })
            }

小结

至此,我们所受写的promise的基本功能算是实现了,但原生的promise还有catch,finally等方法,这些方法将会在下一篇文章中想解,由于文章篇幅与代码量的原因,现将promise的基本功能实现。

promise完整代码(未完成版):

class MyPromise{
    state = "pending"
    result = null
    // then方法传入的回调函数队列
    #handlers = [] // [{onFufilled, onRejected}...]
    // 执行异步任务
    #runAsyncTask(callback) {
        if(typeof queueMicrotask === 'function') {
            // 1.queueMicrotask
            queueMicrotask(callback)
        } else if( typeof MutationObserver === 'function' ) {
            // 2.MutationObserver
            const obv = new MutationObserver(callback)
            const div = document.createElement("div")
            obv.observe(div, { attributes: true })
            div.setAttribute("name", "123")
        } else  {
            // 3.setTimeout
            setTimeout(callback, 0)
        }
    }
    #resolvePromise(promise,res,resolve,reject) {
        // 判断是否重复引用,如果重复引用则抛出错误
        if(res === promise) {
            throw new TypeError("Chaining cycle detected for promise #<Promise>")
        }
        // 如果返回值时promise实例,则需要单独处理
        if(res instanceof MyPromise) {
            res.then(resolve, reject)
        } else {
            resolve(res)
        }
    }
    // 构造函数
    constructor(callback) {
        // 为回调函数传入resolve和reject方法
        const resolve = (result)  => {
            // 状态变为resolved
            if(this.state === "pending") {
                this.state = "fufilled"
                this.result = result
                // 执行回调队列
                this.#handlers.forEach((item) => {
                    item.onFufilled(this.result)
                })
            }
        }
        const reject = (result) =>  {
            // 状态变为rejected
            if(this.state === "pending") {
                this.state = "rejected"
                this.result = result
                // 执行回调队列
                this.#handlers.forEach((item) => {
                    item.onRejected(this.result)
                })
            }
        }
        callback(resolve, reject)
    }
    // then方法
    then(onFufilled, onRejected) {
        // 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
        if(typeof onFufilled !== "function") {
            onFufilled = (x) => x
        }
        if(typeof onRejected !== "function") {
            onRejected = (x) => { throw(x) }
        }
        // 创建一个用于返回的promise实例
        const newP = new MyPromise((resolve,reject) => {
            // 执行对应的回调
            if(this.state === "fufilled") {
                // 执行异步任务
                this.#runAsyncTask(() => {
                    // 捕获错误
                    try {
                        // 接收成功回调的返回值,作为下一个then的参数
                        const res = onFufilled(this.result)
                        // 判断是否引用,是否是promise实例
                        this.#resolvePromise(newP, res, resolve, reject)
                    } catch(e) {
                        reject(e)
                    }
                })
            } else if(this.state === "rejected") {
                // 执行异步任务
                this.#runAsyncTask(() => {
                    try{
                        const res = onRejected(this.result)
                        this.#resolvePromise(newP, res, resolve, reject)
                    } catch(err) {
                        reject(err)
                    }
                })
            } else if(this.state === "pending") {
                // 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
                this.#handlers.push({
                    onFufilled: () => {
                        this.#runAsyncTask(() => {
                            try {
                                const res = onFufilled(this.result)
                                this.#resolvePromise(newP, res, resolve, reject)
                            }catch(err) {
                                reject(err)
                            }
                        })
                    },
                    onRejected: () => {
                        this.#runAsyncTask(() => {
                            try {
                                const res = onRejected(this.result)
                                this.#resolvePromise(newP, res, resolve, reject)
                            } catch(err) {
                                reject(err)
                            }
                        })
                    }
                })
            }
        })
        // 方法结束返回新的promise实例
        return newP
    }
}

  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值