手写Promise(前端面试题)第二篇 ·实例方法与静态方法

目录

1.实例方法catch

2.finally方法

3.MyPromise.resolve()

4.MyPromise.reject()

5.MyPromise.race()

6.MyPromise.all()

7.MyPromise.allSettled()

8.MyPromise.any()

总结


1.实例方法catch

在promise中,每一个promise实例都有一个catch方法,该方法接收一个回调函数,用于当promise的状态是rejected时执行对应的逻辑,同时它也可以获取到构造函数中的错误,并通过error参数注入到回调函数中。这里参考了MDN中promise的catch方法的解释,直接调用then方法,并在第二个参数中传入对应的处理逻辑即可。

catch方法的解释: Promise.prototype.catch() - JavaScript | MDN (mozilla.org)

代码如下:

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

构造函数中需要捕获错误;

try {
   callback(resolve, reject)
} catch(err) {
   reject(err)
}

2.finally方法

promise实例的finally方法无论promise的状态是成功还是失败,都会执行的一个方法,其需要传入一个回调函数,回调函数不需要出入任何参。这里参考MDN对promise的finally的描述,其相当于调用了then方法,同时传入的两个回调完全一样,这样无论是那种状态,都会执行里面的逻辑。同时finally和then方法一样,也返回一个promise实例。

finally的描述:Promise.prototype.finally() - JavaScript | MDN (mozilla.org)

代码如下:

finally(onFinally) {
   return this.then(onFinally, onFinally)
}

3.MyPromise.resolve()

原生Promise类的静态方法resolve可以接受一个基础值,内部将其会转为一个成功的promise实例并返回,但需要注意的是,如果传入值是一个promise实例,则会将其直接返回,不做任何处理。(此处参考MDN文档)

Promise.resolve解释:Promise.resolve() - JavaScript | MDN (mozilla.org)

代码如下;

static resolve(value) {
   // 判断传入的值是否为promise实例
   if(value instanceof MyPromise) {
         return value
     } else {
         return new MyPromise((resolve, reject) => {
           resolve(value)
         })
    }
}

4.MyPromise.reject()

此方法与MyPromise.revolve()显相似,唯一不同的是这个方法不需要判断是否为promise实例,而是无论传入的是什么,直接返回一个拒绝的promise实例。

static reject(value) {
   return new MyPromise((resolve, reject) => {
      reject(value)
  })
}

5.MyPromise.race()

Promise.race()方法用于处理多个异步任务。其接收一个数组作为参数,数组每一项可以是promise实例,也可以时普通值。如果传入的不是一个数组,则会报错,错误信息是:Argument is not iterable。传入的每一项如果是promise实例,则会等待第一个promise状态敲定,然后返回一个与第一个敲定的promise实例状态相同的promise实例。如果数组中某些项不是promise实例,则会其包装成一个成功的promise实例,并在其敲定是返回一个成功的promise实例。

参考MDN:Promise.race() - JavaScript | MDN (mozilla.org)

代码如下:

static race(arr) {
        return new MyPromise((resolve, reject) => {
            // 判断是否传入数组参数
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 等待第一个promise执行完毕
            for(let item of arr) {
                MyPromise.resolve(item).then((res) => {
                    resolve(res)
                },err => {
                    reject(err)
                })

            }
        })
    }

6.MyPromise.all()

Promise.all()方法接收一个promise数组,返回一个promise实例,当数组中所有的promise实例全部成功之后,将所有的promise的结果存入一个数组中并作为返回的promise的结果,当有任何一个promise拒绝之后,将第一个拒绝的promise的原因作为返回的promise的原因。需要注意的是,这个方法和Promise.race()一样,如果传入的不是数组(本质上是可迭代对象),就会报错。如果传入的是空数组,就会将空数组作为结果直接返回,如果传入的数组中有普通值,则将会被包装promise实例。同时在返回的结果数组中,其结果的顺序需要与传入的promise数组中对应相同。

参考MDN:Promise.all() - JavaScript | MDN (mozilla.org)

代码如下:

static all(arr) {
        return new MyPromise((resolve,reject) => {
            // 判断是否是数组
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 如果是空数组,则直接返回一个成功状态的promise
            if(arr.length === 0) resolve([])
            // 定一个数组,用于存储结果
            const resArr = []
            // 兑现次数
            let count = 0
            // 遍历数组
            arr.forEach((item,index) => {
                 MyPromise.resolve(item).then((res) => {
                    // 添加到结果数组中
                    resArr[index] = res
                    // 计数器+1
                    count++
                    // 判断是否全部执行完毕
                    if(count === arr.length) {
                        resolve(resArr)
                    }
                },err => {
                    reject(err)
                })
            })
        })
    }

 这个方法中,在判断是否所有的promise实例都敲定时,不能通过resArr的长度来判断,因为我们不知道是哪个promise实例先敲定,如果是后面的某个先敲定,则数组中除了敲定的那个promise的结果外,有可能就会含有空值,这样数组长度就变得不可信,因此我们需要手动定义一个计数器来判断是否全部敲定。

7.MyPromise.allSettled()

Promise.allSettled()方法和all方法很像,不同之处在于这个方法无论传入数组中的promise时成功还是失败,都会将其结果信息存入数组中,等到所有的promise实例都敲定,就会兑现一个promise实例,结构就是数组中每个项的结果信息。

参考:Promise.allSettled() - JavaScript | MDN (mozilla.org)

代码如下:

static allSettled(arr) {
        return new MyPromise((resolve, reject) => {
            // 判断是否传入数组参数
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 判断传入的参数是否为空数组
            if(arr.length === 0) resolve([])
            // 定义一个结果数组
            const resArr = []
            // 计数器
            let count = 0
            // 遍历数组
            arr.forEach((item,index) => {
                MyPromise.resolve(item).then((res) => {
                    // 添加到结果数组中
                    resArr[index] = {status: "fulfilled", value: res}
                    count++
                    if(count === arr.length) resolve(resArr)
                },err => {
                    // 添加到结果数组中
                    resArr[index] = {status: "rejected", reason: err}
                    count++
                    if(count === arr.length) resolve(resArr)
                })
            })
    })

    }

8.MyPromise.any()

Promise.any()这个方法的功能和all完全相反,它是等待所有的promise都拒绝,然后返回一个拒绝的promise,如果只要有一个成功,则返回一个成功的promise。同时它所返回的拒绝信息本质上是一个AggregateError类型的错误,这个错误中包含了所有的promise拒绝的原因。如果传入空数组,直接将空数组作为原因返回。

MDN参考:Promise.any() - JavaScript | MDN (mozilla.org)

代码如下:

static any(arr) {
        return new MyPromise((resolve, reject) => {
            // 判断是否为数组
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 判断是否为空数组
            if(arr.length === 0) {
                const error = new AggregateError([],"All promises were rejected")
                return reject(error)
            }
            // 定义结结果数组
            const errArr = []
            // 定义一个计数器
            let count = 0
            // 遍历数组
            arr.forEach((item,index) => {
                MyPromise.resolve(item).then((res) => {
                    resolve(res)
                },err => {
                    errArr[index] = err
                    count++
                    if(count === arr.length) {
                        const error = new AggregateError(errArr,"All promises were rejected")
                        reject(error)
                    }
                })
            })
        })
    }

总结

至此,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实例,则需要单独处理
        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)
                })
            }
        }
        // 这里需要处理异常,将异常信息注入catch方法中
        try {
            callback(resolve, reject)
        } catch(err) {
            reject(err)
        }
        
    }
    // 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
    }
    // catch方法
    catch(onRejected) {
        return this.then(null, onRejected)
    }
    //finally方法
    finally(onFinally) {
        return this.then(onFinally, onFinally)
    }
    // 静态方法
    static resolve(value) {
        // 判断传入的值是否为promise实例
        if(value instanceof MyPromise) {
            return value
        } else {
            return new MyPromise((resolve, reject) => {
                resolve(value)
            })
        }
    }

    static reject(value) {
        return new MyPromise((resolve, reject) => {
            reject(value)
        })
    }

    static all(arr) {
        return new MyPromise((resolve,reject) => {
            // 判断是否是数组
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 如果是空数组,则直接返回一个成功状态的promise
            if(arr.length === 0) resolve([])
            // 定一个数组,用于存储结果
            const resArr = []
            // 兑现次数
            let count = 0
            // 遍历数组
            arr.forEach((item,index) => {
                 MyPromise.resolve(item).then((res) => {
                    // 添加到结果数组中
                    resArr[index] = res
                    // 计数器+1
                    count++
                    // 判断是否全部执行完毕
                    if(count === arr.length) {
                        resolve(resArr)
                    }
                },err => {
                    reject(err)
                })
            })
        })
    }

    static race(arr) {
        return new MyPromise((resolve, reject) => {
            // 判断是否传入数组参数
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 等待第一个promise执行完毕
            for(let item of arr) {
                MyPromise.resolve(item).then((res) => {
                    resolve(res)
                },err => {
                    reject(err)
                })

            }
        })
    }

    static allSettled(arr) {
        return new MyPromise((resolve, reject) => {
            // 判断是否传入数组参数
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 判断传入的参数是否为空数组
            if(arr.length === 0) resolve([])
            // 定义一个结果数组
            const resArr = []
            // 计数器
            let count = 0
            // 遍历数组
            arr.forEach((item,index) => {
                MyPromise.resolve(item).then((res) => {
                    // 添加到结果数组中
                    resArr[index] = {status: "fulfilled", value: res}
                    count++
                    if(count === arr.length) resolve(resArr)
                },err => {
                    // 添加到结果数组中
                    resArr[index] = {status: "rejected", reason: err}
                    count++
                    if(count === arr.length) resolve(resArr)
                })
            })
    })

    }

    static any(arr) {
        return new MyPromise((resolve, reject) => {
            // 判断是否为数组
            if(!Array.isArray(arr)) {
                return reject(new TypeError("Argument is not iterable"))
            }
            // 判断是否为空数组
            if(arr.length === 0) {
                const error = new AggregateError([],"All promises were rejected")
                return reject(error)
            }
            // 定义结结果数组
            const errArr = []
            // 定义一个计数器
            let count = 0
            // 遍历数组
            arr.forEach((item,index) => {
                MyPromise.resolve(item).then((res) => {
                    resolve(res)
                },err => {
                    errArr[index] = err
                    count++
                    if(count === arr.length) {
                        const error = new AggregateError(errArr,"All promises were rejected")
                        reject(error)
                    }
                })
            })
        })
    }
}

你可能感兴趣的:(手写promise,前端,javascript,面试)