【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)

简介: 【面试题】吃透Promise?先实现一个再说(包含所有方法)(二)

【面试题】吃透Promise?先实现一个再说(包含所有方法)(一):https://developer.aliyun.com/article/1413943

2.3.2是针对x如果是一个promise对象

需要通过对PENDING,FULFILLED,REJECTED3个状态进行 如果x处于pending状态,那么在成功或者失败前,我们需要保存这个状态, 如果x处于fulfilled或者rejected状态我们只需要重新resove或者reject出去即可

2.3.3如果x一个对象或者函数 如果x不是一个对象或者函数,那么为普通值我们直接resolve出去

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    } else {
        resolve(x)
    }
}
复制代码

初步的模型已经完成,此时关于前面then的穿透问题可以大致看出来已经解决

  let p1 = new MyPromise((resolve, reject) => {
                resolve('成功')
            })
    .then()
    .then()
    .then(
        (value) => {
            console.log(value) // 成功
        },
        (err) => {
            console.log(err)
        }
    )
复制代码

接下来继续按照规范来,从2.3.1处开始处理对象或者函数的情况 2.3.1说假设x有一个then属性, 2.3.2:在读取属性的时候如果抛出异常则reject出去(Object.defineProerty(x,'then',{get(){throw new Error('err')}})) 则代码如下

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
        } catch (e) {
            reject(e)
        }
    } else {
        resolve(x)
    }
}
复制代码

2.3.3开始对then进行处理,如果then是一个函数则认为x是一个promise对象,然后调用它(

If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:)并且附带2个参数(函数)处理resolve(参数y)和reject(参数r)这里指的是2.3.3.3.1和2.3.3.3.2 2.3.3.3的意思是如果r和y被多次调用或者对某个函数重复调用,第一次调用优先,其他忽略,因此我们指定一个全局变量called来控制调用

2.3.3.4的意思是如果调用后抛出异常,这个异常可能在调用y或者r函数后造成也可能是在之前就抛出的 因此也需要使用called来控制是否抛出异常 2.3.4以及后面的指的是如果then不是一个函数或者对象,那么确定fulfilled状态resolve出去即可 至此完整resolvePromise函数封装如下

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    let called = false
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
            if (typeof then === 'function') {
                then.call(
                    x,
                    (y) => {
                        if (called) return
                        called = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    (r) => {
                        if (called) return
                        called = true
                        reject(r)
                    }
                )
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}
复制代码

对了突然想起来对于executor函数中的resolve封装中,如果resolve里面是多层嵌套的promsie对象的话例如这样

let p1 = new Promise((resolve, reject) => {
        resolve(
            new Promise((resolve, reject) => {
                resolve(11)
            })
        )
    }).then((value) => {
        console.log(value)
    })
复制代码

我们需要对resolve的参数做一个提前的判断处理,如果是promsie的实例我们应该调用then方法 添加起来非常简单,代码如下

 const resolve = (value) => {
            if (value instanceof MyPromise) {
                value.then(resolve, reject)
                return
            }
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach((fn) => fn())
            }
        }
复制代码

至此完整的promise实现代码如下

const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED']
class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) => {
            if (value instanceof MyPromise) {
                value.then(resolve, reject)
                return
            }
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach((fn) => fn())
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach((fn) => fn())
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
        onRejected =
            typeof onRejected === 'function'
                ? onRejected
                : (reason) => {
                      throw reason
                  }
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }
            if (this.status === PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
            }
        })
        return promise2
    }    
}
function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    let called = false
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
            if (typeof then === 'function') {
                then.call(
                    x,
                    (y) => {
                        if (called) return
                        called = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    (r) => {
                        if (called) return
                        called = true
                        reject(r)
                    }
                )
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}
复制代码

3.Promise的静态方法以及原型方法

 console.log(Reflect.ownKeys(Promise))
 console.log(Promise.prototype)
复制代码

通过上述代码可查看Promise的静态方法以及原型上的方法

话不多说直接动手开干,都走到这一步了,麻烦亲坚持一下

3.1-Promise.resolve/Promise.reject

这2个方法比较简单,直接调用我们之前封装好的promsie里面的resolve和reject函数即可

   static resolve(value) {
        return new MyPromise((resolve, reject) {
            resolve(value)
        })
    }
    static reject(reason) {
        return new MyPromise((resolve, reject) => {
            reject(reason)
        })
    }
复制代码

3.2-Promise.all

`不了解的同学点击参考MDN-Promise.all

如果参数不是一个可迭代的对象,那么会报错,并且返回的值需要拿promise包裹出去,而且顺序不能变需要注意 代码如下

  static all(promiseArr) {
        let resArr = [],
            idx = 0
        if (!isIterable(promiseArr)) {
            let type = typeof promiseArr
            throw TypeError(`${type} is not a iterable (cannot read property Symbol(Symbol.iterator))
    at Function.all (<anonymous>)`)
        }
        return new Promise((resolve, reject) => {
            promiseArr.map((promise, index) => {
                if (isPromise(promise)) {
                    promise.then((res) => {
                        formatArr(res, index, resolve)
                    }, reject)
                } else {
                    formatArr(promise, index, resolve)
                }
            })
        })
        function formatArr(value, index, resolve) {
            resArr[index] = value
            // if(resArr.length ===promiseArr.length) 在某些时刻不正确,比如数组最后一项先执行完 数组就为[empty,empty,value]
            if (++idx === promiseArr.length) {
                resolve(resArr)
            }
        }
    }
//工具函数封装
function isIterable(value) {
    return value !== null && value !== undefined && typeof value[Symbol.iterator] === 'function'
}
function isPromise(x) {
    if ((typeof x === 'object' && x !== null) || typeof x == 'function') {
        let then = x.then
        return typeof then === 'function'
    }
    return false
}
复制代码

3.3-Promise.allSettled

与promise.all的实现思想大差不多,只不过返回的数组里面包含的表明状态的对象,而且不管是成功或者失败都收集起来

static allSettled(promiseArr) {
        let resArr = [],
            idx = 0
        if (!isIterable(promiseArr)) {
            let type = typeof promiseArr
            throw TypeError(`${type} is not a iterable (cannot read property Symbol(Symbol.iterator))
    at Function.all (<anonymous>)`)
        }
        return new MyPromise((resolve, reject) => {
            if (promiseArr.length === 0) {
                resolve([])
            }
            promiseArr.forEach((promise, index) => {
                if (isPromise(promise)) {
                    promise.then(
                        (value) => {
                            formatArr('fulfilled', value, index, resolve)
                        },
                        (err) => {
                            formatArr('rejected', err, index, resolve)
                        }
                    )
                } else {
                    formatArr('fulfilled', promise, index, resolve)
                }
            })
        })
        function formatArr(status, value, index, resolve) {
            switch (status) {
                case 'fulfilled':
                    resArr[index] = {
                        status,
                        value
                    }
                    break
                case 'rejected':
                    resArr[index] = {
                        status,
                        reason: value
                    }
                    break
                default:
                    break
            }
            if (++idx === promiseArr.length) {
                resolve(resArr)
            }
        }
    }
复制代码

3.4-Promise.race

这个方法也比较简单,race是赛跑的意思,当某一项确定状态后,直接包装成promise出去就好

 static race(promiseArr) {
        if (!isIterable(promiseArr)) {
            let type = typeof promiseArr
            throw TypeError(`${type} is not a iterable (cannot read property Symbol(Symbol.iterator))
    at Function.all (<anonymous>)`)
        }
        return new Promise((resolve, reject) => {
            promiseArr.forEach((promise, index) => {
                if (isPromise(promise)) {
                    promise.then(resolve, reject)
                } else {
                    resolve(promise)
                }
            })
        })ise.then
    }
复制代码

3.4-Promise.prototype.finally

这个方法其实要考虑的因素蛮多,在此我列举出来 finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

finally本质上是then方法的特例。

  1. finally 无论外面的promise状态成功还是失败 都要走 并且回调函数不带参数
  2. 正常走finallu之后then 或者 catch
  3. 如果finally 内部有promise 并且有延时处理,整个finall会等待执行
  4. 如果2个都是成功,取外面结果
  5. 如果外面是成功 里面是失败,取里面失败的结果
  6. 如果外面是失败 里面是成功 取外面失败的结果
  7. 如果外面和里面都是失败,取里面失败的结果
  8. 如果外面成功,里面成功,取外面成功的结果

我们首先要把上一次promise的值保存下来 这样只有当里面是失败的情况下,才取finally内部失败的值,其余取上一个promise的值

 finally(callbacks) {
        return this.then(
            (value) => {
                return MyPromise.resolve(callbacks()).then(() => value)
            },
            (err) => {
                return MyPromise.resolve(callbacks()).then(() => {
                    throw err
                })
            }
        )
    }
复制代码

3.5-Promise.prototype.catch

这个方法比较简单,相当于调用then的第二个回调而已

 catch(callback) {
        return this.then(null.callback)
    }
复制代码

完整带代码 链接 大哥看完麻烦给个star,创作不易,谢谢!!!!!!!!!

参考链接2:promisesaplus.com/ (promiseA+规范)

末尾

本文只是基于romiseA+规范进行实现,掌握可胜任工作以及,中等偏下的面试题,但是与v8引擎下的promise规范还是有很多差异 掌握本文可参考月夕大佬的promise文章 参考链接1:juejin.cn/post/705520… (ECMA规范与V8引擎下的promise)

给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章
|
2月前
|
存储 前端开发 JavaScript
【面试题】Promise只会概念远远不够,还需这17道题目巩固!
【面试题】Promise只会概念远远不够,还需这17道题目巩固!
|
2月前
|
存储 JavaScript 前端开发
【面试题】JS的14种去重方法,看看你知道多少(包含数组对象去重)
【面试题】JS的14种去重方法,看看你知道多少(包含数组对象去重)
|
2月前
|
存储 前端开发 JavaScript
【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?
【面试题】面试官问:如果有100个请求,你如何使用Promise控制并发?
|
2月前
|
前端开发 JavaScript API
【面试题】说说 Promise是什么?如何使用
【面试题】说说 Promise是什么?如何使用
|
2月前
|
前端开发 JavaScript
【面试题】async/await、promise和setTimeout的执行顺序
【面试题】async/await、promise和setTimeout的执行顺序
|
27天前
|
算法 数据挖掘 大数据
深入解析力扣172题:阶乘后的零(计算因子5的方法详解及模拟面试问答)
深入解析力扣172题:阶乘后的零(计算因子5的方法详解及模拟面试问答)
|
2月前
|
JavaScript
分享经典面试题:JS数组去重的多种方法
分享经典面试题:JS数组去重的多种方法
|
2月前
|
数据采集 Python
2024年Python最新【Python基础教程】快速找到多个字典中的公共键(key)的方法,秋招面试问题
2024年Python最新【Python基础教程】快速找到多个字典中的公共键(key)的方法,秋招面试问题
2024年Python最新【Python基础教程】快速找到多个字典中的公共键(key)的方法,秋招面试问题
|
2月前
|
程序员 PHP Python
2024年Python最全Python基础教程:keys()、values()和 items()方法,百度面试题php
2024年Python最全Python基础教程:keys()、values()和 items()方法,百度面试题php
2024年Python最全Python基础教程:keys()、values()和 items()方法,百度面试题php
|
27天前
|
SQL 算法 大数据
深入解析力扣181题:超过经理收入的员工(自连接方法详解及模拟面试问答)
深入解析力扣181题:超过经理收入的员工(自连接方法详解及模拟面试问答)