【面试题】吃透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方法的特例。
- finally 无论外面的promise状态成功还是失败 都要走 并且回调函数不带参数
- 正常走finallu之后then 或者 catch
- 如果finally 内部有promise 并且有延时处理,整个finall会等待执行
- 如果2个都是成功,取外面结果
- 如果外面是成功 里面是失败,取里面失败的结果
- 如果外面是失败 里面是成功 取外面失败的结果
- 如果外面和里面都是失败,取里面失败的结果
- 如果外面成功,里面成功,取外面成功的结果
我们首先要把上一次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、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库