努力让学习成为一种习惯,自信来源于充分的准备
如果你觉得该文章对你有帮助,欢迎大家点赞、关注和分享
前言
在上篇文章 深入理解Promise-中(透析篇)中我们实现了 Promise A+规范核心部分的 promise,但是相比于我们日常使用的ES6中的promise还少了些功能,这篇文章我们把其常用的功能都完善下
为了方便阅读,我把上篇文章中Promise实现的完整代码挪过来
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function isPromiseLike(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
class MyPromise {
#state = PENDING
#result = undefined
#promiseHandler = []
constructor(executor) {
if (typeof executor !== 'function') {
throw new Error('is not a function')
}
const resolve = (result) => {
this.#statusChangeHandler(FULFILLED, result)
}
const reject = (result) => {
this.#statusChangeHandler(REJECTED, result)
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
#statusChangeHandler(status, result) {
if (this.#state !== PENDING) return
this.#state = status
this.#result = result
this.#run()
}
#microTaskRun(cb) {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(cb)
} else if (typeof MutationObserver === 'function') {
const textNode = document.createTextNode('')
const observer = new MutationObserver(cb)
observer.observe(textNode, {
characterData: true
})
textNode.data = 1
} else {
setTimeout(cb, 0);
}
}
#run() {
if (this.#state === PENDING) return
while(this.#promiseHandler.length) {
this.#thenableHandler(this.#promiseHandler.shift())
}
}
#thenableHandler({onFulfilled, onRejected, resolve, reject}) {
this.#microTaskRun(() => {
let cb = this.#state === FULFILLED ? onFulfilled : onRejected
if (typeof cb !== 'function') {
cb = this.#state === FULFILLED ? () => this.#result : () => { throw this.#result }
}
try {
const r = cb(this.#result)
if (isPromiseLike(r)) {
r.then(resolve, reject)
} else {
resolve(r)
}
} catch(e) {
reject(e)
}
})
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#promiseHandler.push({
onFulfilled,
onRejected,
resolve,
reject
})
this.#run()
})
}
}
Promise.resolve/reject
class MyPromise {
// ...
static resolve(v) {
if (v instanceof MyPromise) {
return v
} else if (isPromiseLike(v)) {
return new MyPromise((resolve, reject) => {
v.then(resolve, reject)
})
} else {
return new MyPromise(resolve => {
resolve(v)
})
}
}
static reject(v) {
return new MyPromise((resolve, reject) => {
reject(v)
})
}
}
// 测试例子
MyPromise.resolve(1).then(v => {
console.log(v); // 1
})
const p = new MyPromise((resolve, reject) => {
resolve(25)
})
MyPromise.resolve(p).then(v => {
console.log(v); // 25
})
const pl = {
then(resolve, reject) {
reject(188)
}
}
MyPromise.resolve(pl).then(null, v2 => {
console.log(v2); // 188
})
MyPromise.reject(1).then(null, (v) => {
console.log(v);
})
Promise.catch
class MyPromise {
// ...
catch(onRejected) {
return this.then(null, onRejected)
}
}
// 测试例子
const p = new MyPromise((resolve, reject) => {
resolve(1)
})
p.then(v => {
console.log(v); // 1
throw 1
}).catch(err => {
console.log(err); // 1
return 11
}).then(v => {
console.log(v); // 11
})
const p2 = MyPromise.reject(2)
p.then(v => {
return p2
}).catch(err => {
console.log(err) // 2
})
const p3 = MyPromise.reject(123)
MyPromise.resolve(p3).then().catch(e => {
console.log(e); // 123
})
Promise.finally
class MyPromise {
// ...
finally(cb) {
return this.then(cb, cb)
}
}
Promise.all
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
class MyPromise {
// ...
static all(promiseArr) {
return new MyPromise((resolve, reject) => {
// promiseArr数据类型校验 - 必须具有 Iterator 接口
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
// 如果是可迭代对象,将其转化成数组。然后确保每一项都是Promise
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
const len = _promiseArr.length
if (len === 0) {
resolve([])
return
}
let resolveNum = 0
const promiseResults = []
_promiseArr.forEach((p, index) => {
p.then(v => {
// 这里需要注意用下标存储每个promise resolve的结果,而不要用push
// 否则可能会造成输出结果的顺序和传入Promise顺序不一致
promiseResults[index] = v
// 只有所有的promise状态均为fulfilled,该promise的状态才会为fulfilled
if (++resolveNum === len) {
resolve(promiseResults)
}
},
// 对于reject,只要其中一个被拒绝了,该promise的状态就会为rejected。所以直接传入即可
//(因为promise的状态一旦修改便不可改变)
reject
)
});
})
}
}
// 测试例子
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0);
})
const p2 = MyPromise.resolve(12)
const p3 = MyPromise.resolve(13)
MyPromise.all([p1, p2, p3]).then(res => {
console.log(res); // [1,12,13]
})
MyPromise.all([p1, p2, p3, MyPromise.reject(11)]).then(res => {
console.log(res); // 不会执行
}).catch(err => {
console.log(err) // 11
})
MyPromise.all([]).then(res => {
console.log(res) // []
})
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
MyPromise.all(myIterable).then(res => {
console.log(res) // [1,2,3]
})
这里额外提一句,上面测试例子整体输出如下(具体原因涉及到事件循环了,这个后续会单独出篇文章讲解,这里只关注Promise功能本身。不关注多个Promise实例,代码的执行顺序)
Promise.any
Promise.any与Promise.all是镜像关系。当任何一个参数实例状态为fulfilled,包装实例的状态为fulfilled。当全部参数实例状态为rejected,包装实例的状态为fulfilled
class MyPromise {
// ...
static any(promiseArr) {
return new MyPromise((resolve, reject) => {
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
const len = _promiseArr.length
if (len === 0) {
reject(new AggregateError([]))
return
}
let rejectNum = 0
const reasons = []
_promiseArr.forEach(p => {
p.then(resolve, (reason) => {
reasons.push(reason)
if (++rejectNum === len) {
reject(new AggregateError(reasons))
}
},
)
});
})
}
}
// 测试例子
MyPromise.any([MyPromise.reject(2), MyPromise.reject(2)]).catch(err => {
console.log(err); // AggregateError: All promises were rejected
})
MyPromise.any([1, MyPromise.reject(2)]).then(res => {
console.log(res) // 1
})
Promise.allSettled
class MyPromise {
// ...
static allSettled(promiseArr) {
return new MyPromise((resolve, reject) => {
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
const len = _promiseArr.length
if (len === 0) {
resolve([])
return
}
let num = 0
const allResults = []
_promiseArr.forEach((p, index) => {
p.then(value => {
allResults[index] = {
status: FULFILLED,
value
}
}, (reason) => {
allResults[index] = {
status: REJECTED,
reason
}
}).finally(() => {
if (++num === len) {
resolve(allResults)
}
})
});
})
}
}
// 测试例子
MyPromise.allSettled([1,2,MyPromise.reject(1)]).then(res => {
console.log(res)
/* [
{ status: 'fulfilled', value: 1 },
{ status: 'fulfilled', value: 2 },
{ status: 'rejected', reason: 1 }
]
*/
})
Promise.race
class MyPromise {
// ...
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
for (const p of _promiseArr) {
p.then(resolve, reject)
}
})
}
}
// 测试例子
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 100);
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(2)
}, 200);
})
MyPromise.race([p1, p2]).then(res => {
console.log(res); // 1
}).catch(err => {
console.log(err);
})
整体完整版(es6版本)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function isPromiseLike(obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
function isIterable(obj) {
return obj != null && typeof obj[Symbol.iterator] === 'function';
}
class MyPromise {
#state = PENDING
#result = undefined
#promiseHandler = []
constructor(executor) {
if (typeof executor !== 'function') {
throw new Error('is not a function')
}
const resolve = (result) => {
this.#statusChangeHandler(FULFILLED, result)
}
const reject = (result) => {
this.#statusChangeHandler(REJECTED, result)
}
try {
executor(resolve, reject)
} catch(e) {
reject(e)
}
}
#statusChangeHandler(status, result) {
if (this.#state !== PENDING) return
this.#state = status
this.#result = result
this.#run()
}
#microTaskRun(cb) {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(cb)
} else if (typeof MutationObserver === 'function') {
const textNode = document.createTextNode('')
const observer = new MutationObserver(cb)
observer.observe(textNode, {
characterData: true
})
textNode.data = 1
} else {
setTimeout(cb, 0);
}
}
#run() {
if (this.#state === PENDING) return
while(this.#promiseHandler.length) {
this.#thenableHandler(this.#promiseHandler.shift())
}
}
#thenableHandler({onFulfilled, onRejected, resolve, reject}) {
this.#microTaskRun(() => {
let cb = this.#state === FULFILLED ? onFulfilled : onRejected
if (typeof cb !== 'function') {
cb = this.#state === FULFILLED ? () => this.#result : () => { throw this.#result }
}
try {
const r = cb(this.#result)
if (isPromiseLike(r)) {
r.then(resolve, reject)
} else {
resolve(r)
}
} catch(e) {
reject(e)
}
})
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#promiseHandler.push({
onFulfilled,
onRejected,
resolve,
reject
})
this.#run()
})
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(cb) {
return this.then(cb, cb)
}
static resolve(v) {
if (v instanceof MyPromise) {
return v
} else if (isPromiseLike(v)) {
return new MyPromise((resolve, reject) => {
v.then(resolve, reject)
})
} else {
return new MyPromise(resolve => {
resolve(v)
})
}
}
static reject(v) {
return new MyPromise((resolve, reject) => {
reject(v)
})
}
static all(promiseArr) {
return new MyPromise((resolve, reject) => {
// promiseArr数据类型校验 - 必须具有 Iterator 接口
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
// 如果是可迭代对象,将其转化成数组。然后确保每一项都是Promise
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
const len = _promiseArr.length
if (len === 0) {
resolve([])
return
}
let resolveNum = 0
const promiseResults = []
_promiseArr.forEach((p, index) => {
p.then(v => {
// 这里需要注意用下标存储每个promise resolve的结果,而不要用push
// 否则可能会造成输出结果的顺序和传入Promise顺序不一致
promiseResults[index] = v
// 只有所有的promise状态均为fulfilled,该promise的状态才会为fulfilled
if (++resolveNum === len) {
resolve(promiseResults)
}
},
// 对于reject,只要其中一个被拒绝了,该promise的状态就会为rejected。所以直接传入即可
//(因为promise的状态一旦修改便不可改变)
reject
)
});
})
}
static any(promiseArr) {
return new MyPromise((resolve, reject) => {
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
const len = _promiseArr.length
if (len === 0) {
reject(new AggregateError([]))
return
}
let rejectNum = 0
const reasons = []
_promiseArr.forEach(p => {
p.then(resolve, (reason) => {
reasons.push(reason)
if (++rejectNum === len) {
reject(new AggregateError(reasons))
}
},
)
});
})
}
static allSettled(promiseArr) {
return new MyPromise((resolve, reject) => {
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
const len = _promiseArr.length
if (len === 0) {
resolve([])
return
}
let num = 0
const allResults = []
_promiseArr.forEach((p, index) => {
p.then(value => {
allResults[index] = {
status: FULFILLED,
value
}
}, (reason) => {
allResults[index] = {
status: REJECTED,
reason
}
}).finally(() => {
if (++num === len) {
resolve(allResults)
}
})
});
})
}
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
if(!isIterable(promiseArr)) {
reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
return
}
const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
for (const p of _promiseArr) {
p.then(resolve, reject)
}
})
}
}
最后
本篇文章,我们进一步完善了Promise,手写了es6中的Promise各种方法。其实难度并不大。相信跟着到这里的小伙伴对Promise内部的各种原理都有了比较清晰的了解甚至完全掌握了。但是有一个点在文章中提过一嘴,没有展开讲。那就是多个Promise实例间整体代码的执行顺序。这就涉及到事件循环了,后续会专门出一篇文章😄
到这里,就是本篇文章的全部内容了
如果你觉得该文章对你有帮助,欢迎大家点赞、关注和分享
如果你有疑问或者出入,评论区告诉我,我们一起讨论