以前学习过手写Promise,但是只是在看懂的基础上加以"抄袭"代码。所以,下面一篇文章将总结老师的手写思想,然后记录一下。
promise基础用法如果不熟悉的同学,请访问这里promise用法。
手写之前我们需要知道的事情
- promise初始状态为pending。
- 当调用resolve时,状态变为fulfilled。
- 当调用reject是,状态变为rejected。
- 只要状态从pending改变后,该状态将一直保持不变。
- 抛出错误后,它将调用reject。状态变为rejected。 根据上面的规则,我们可以给出以下结构
- 定义三个状态常量。
- 定义成功传递的值。
- 定义失败的原因。
const PROMISE_STATUS_PENDING = 'pending' const PROMISE_STATUS_FULFILLED = 'fulfilled' const PROMISE_STATUS_REJECTED = 'rejected' class MyPromise { constructor(executor) { this.status = PROMISE_STATUS_PENDING this.value = undefined this.reason = undefined const resolve = (value) => { // 只有状态为pedding时,他才会赋值value,并且改变状态。 if (this.status === PROMISE_STATUS_PENDING) { this.status = PROMISE_STATUS_FULFILLED this.value = value console.log("resolve被调用") } } const reject = (reason) => { if (this.status === PROMISE_STATUS_PENDING) { this.status = PROMISE_STATUS_REJECTED this.reason = reason console.log("reject被调用") } } // 立即执行执行器,即传入promise的函数。当执行器抛出错误,直接调用reject传递错误。 try { executor(resolve, reject) } catch (error) { reject(error) } } }
测试
const promise = new MyPromise((resolve, reject) => { console.log("状态pending") resolve(1111) reject(2222) })
网络异常,图片无法展示
|
由上面的输出可以知道,当调用resolve时,promise状态改变,即使调用了reject了,也不会再去执行。
then方法的设计
- then方法可以接受两个参数。
- 我们将该参数保留下来,当调用resolve或者reject时,将其加入微任务中,这样就满足了原生Promise调用then方法,将其加入微任务队列的效果。
版本一
const PROMISE_STATUS_PENDING = 'pending' const PROMISE_STATUS_FULFILLED = 'fulfilled' const PROMISE_STATUS_REJECTED = 'rejected' class MyPromise { constructor(executor) { this.status = PROMISE_STATUS_PENDING this.value = undefined this.reason = undefined const resolve = (value) => { if (this.status === PROMISE_STATUS_PENDING) { this.status = PROMISE_STATUS_FULFILLED // 将其将入微任务队列中 queueMicrotask(() => { this.value = value this.onFulfilled(this.value) }); } } const reject = (reason) => { if (this.status === PROMISE_STATUS_PENDING) { this.status = PROMISE_STATUS_REJECTED queueMicrotask(() => { this.reason = reason this.onRejected(this.reason) }) } } try { executor(resolve, reject) } catch (error) { reject(error) } } then(onFulfilled, onRejected) { this.onFulfilled = onFulfilled this.onRejected = onRejected } }
测试一
const promise = new MyPromise((resolve, reject) => { console.log("状态pending") reject(2222) resolve(1111) }) // 调用then方法 promise.then(res => { console.log("res1:", res) return 1111 }, err => { console.log("err:", err) }) promise.then(res => { console.log("res2:", res) }, err => { console.log("err2:", err) })
网络异常,图片无法展示
|
由上面的结果可知,当多次调用同一个promise时,他不会多次执行then方法,因为他会覆盖上一次的onFulfilled, onRejected方法,所以只会执行一次。
测试二
// 异步调用then方法 setTimeout(() => { promise.then(res => { console.log("res", res) }) }, 1000);
网络异常,图片无法展示
|
由上面的结果可知,方promise执行完后,才回去执行setTimeout,所以还没有加入到onFulfilled,所以onFulfilled为undefined。
版本二
针对上面的问题,我们可以做出优化
- 定义onFulfilledFns,onRejectedFns两个数组,分别收集then方法对应的两个参数。
- 将then方法传递的参数,放在对应的数组中。
- 当我们将then方法异步调用时,那时promise的状态已经被确定下来,所以,我们需要将then中方法直接调用。
- 如果改变状态的语句放在微任务内的话,那么非异步调用then也会直接在then方法中执行,如果改变状态的语句放在微任务外部的话,那么调用reject和resolve都会执行。所以就需要给出一个判断
if (this.status !== PROMISE_STATUS_PENDING) return
, 这样当resolve的微任务执行的时候状态就会改变,再次执行reject的微任务时,就直接return, 反之也是一样。
const PROMISE_STATUS_PENDING = 'pending' const PROMISE_STATUS_FULFILLED = 'fulfilled' const PROMISE_STATUS_REJECTED = 'rejected' class MyPromise { constructor(executor) { this.status = PROMISE_STATUS_PENDING this.value = undefined this.reason = undefined this.onFulfilledFns = [] this.onRejectedFns = [] const resolve = (value) => { if (this.status === PROMISE_STATUS_PENDING) { // 添加微任务 queueMicrotask(() => { // 如果状态改变,那么将不会再次改变状态,直接在then方法就调用了传递的参数。 if (this.status !== PROMISE_STATUS_PENDING) return this.status = PROMISE_STATUS_FULFILLED this.value = value this.onFulfilledFns.forEach(fn => { fn(this.value) }) }); } } const reject = (reason) => { if (this.status === PROMISE_STATUS_PENDING) { // 添加微任务 queueMicrotask(() => { if (this.status !== PROMISE_STATUS_PENDING) return this.status = PROMISE_STATUS_REJECTED this.reason = reason this.onRejectedFns.forEach(fn => { fn(this.reason) }) }) } } executor(resolve, reject) } then(onFulfilled, onRejected) { // 如果在then调用的时候, 状态已经确定下来,为了满足异步调用then方法 if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) { onFulfilled(this.value) } if (this.status === PROMISE_STATUS_REJECTED && onRejected) { onRejected(this.reason) } // 将成功回调和失败的回调放到数组中 if (this.status === PROMISE_STATUS_PENDING) { this.onFulfilledFns.push(onFulfilled) this.onRejectedFns.push(onRejected) } } }
测试三
const promise = new MyPromise((resolve, reject) => { console.log("状态pending") resolve(1111) // resolved/fulfilled reject(2222) }) // 调用then方法多次调用 promise.then(res => { console.log("res1:", res) }, err => { console.log("err:", err) }) promise.then(res => { console.log("res2:", res) }, err => { console.log("err2:", err) }) // 在确定Promise状态之后, 再次调用then setTimeout(() => { promise.then(res => { console.log("res3:", res) }, err => { console.log("err3:", err) }) }, 1000)
网络异常,图片无法展示
|
上面版本存在的问题是不能链式调用。
版本三
- then方法直接返回promise。
- 只有当then中的方法抛出异常的时候,他才会调用reject。否则都将调用resolve。
function execFunctionWithCatchError(execFn, value, resolve, reject) { try { const result = execFn(value) resolve(result) } catch(err) { reject(err) } }
const PROMISE_STATUS_PENDING = 'pending' const PROMISE_STATUS_FULFILLED = 'fulfilled' const PROMISE_STATUS_REJECTED = 'rejected' // 工具函数 function execFunctionWithCatchError(execFn, value, resolve, reject) { try { const result = execFn(value) resolve(result) } catch(err) { reject(err) } } class MyPromise { constructor(executor) { this.status = PROMISE_STATUS_PENDING this.value = undefined this.reason = undefined this.onFulfilledFns = [] this.onRejectedFns = [] const resolve = (value) => { if (this.status === PROMISE_STATUS_PENDING) { // 添加微任务 queueMicrotask(() => { if (this.status !== PROMISE_STATUS_PENDING) return this.status = PROMISE_STATUS_FULFILLED this.value = value this.onFulfilledFns.forEach(fn => { fn(this.value) }) }); } } const reject = (reason) => { if (this.status === PROMISE_STATUS_PENDING) { // 添加微任务 queueMicrotask(() => { if (this.status !== PROMISE_STATUS_PENDING) return this.status = PROMISE_STATUS_REJECTED this.reason = reason this.onRejectedFns.forEach(fn => { fn(this.reason) }) }) } } try { executor(resolve, reject) } catch (err) { reject(err) } } then(onFulfilled, onRejected) { return new HYPromise((resolve, reject) => { // 如果在then调用的时候, 状态已经确定下来 if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) { // try { // const value = onFulfilled(this.value) // resolve(value) // } catch(err) { // reject(err) // } execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) } if (this.status === PROMISE_STATUS_REJECTED && onRejected) { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) } // 将成功回调和失败的回调放到数组中 if (this.status === PROMISE_STATUS_PENDING) { // 这里是为了拿到返回的值所以再次封装一层函数 this.onFulfilledFns.push(() => { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) }) this.onRejectedFns.push(() => { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }) } }) } }
测试四
const promise = new MyPromise((resolve, reject) => { console.log("状态pending") resolve(1111) }) // 调用then方法多次调用 promise.then(res => { console.log("res1:", res) return "aaaa" }, err => { console.log("err1:", err) return "bbbbb" }).then(res => { console.log("res2:", res) }, err => { console.log("err2:", err) })
网络异常,图片无法展示
|
上面的then方法没有判断then方法返回值,只是返回普通的值。
catch方法的设计
- 利用then方法链式调用,抛出异常时,直接调用下一个then方法的onRejected。
- 直接定义一个默认的onRejected方法,抛出错误。
- 直接调用then方法的第二个参数即可
then(onFulfilled, onRejected) { // then方法新增 + const defaultOnRejected = err => { throw err } + onRejected = onRejected || defaultOnRejected } catch(onRejected) { // 让其可以链式调用 return this.then(undefined, onRejected) }
测试
const promise = new MyPromise((resolve, reject) => { console.log("状态pending") reject(2222) }) // 调用then方法多次调用 promise.then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
网络异常,图片无法展示
|
finally方法的设计
- 实现原理其实和catch差不多。
- 就是使用then方法的链式调用,在下一个then方法中的参数中执行finally参数。
then(onFulfilled, onRejected) { // then方法新增 + const defaultOnFulfilled = value => { return value } + onFulfilled = onFulfilled || defaultOnFulfilled } finally(onFinally) { this.then(() => { onFinally() }, () => { onFinally() }) }
测试
const promise = new MyPromise((resolve, reject) => { console.log("状态pending") resolve(1111) }) // 调用then方法多次调用 promise.then(res => { console.log("res1:", res) return "aaaaa" }).then(res => { console.log("res2:", res) }).catch(err => { console.log("err:", err) }).finally(() => { console.log("finally") })
网络异常,图片无法展示
|
resolve, reject方法设计
- 直接调用Promise即可。然后再调用相应的resolve, reject方法。
static resolve(value) { return new MyPromise((resolve) => resolve(value)) } static reject(reason) { return new MyPromise((resolve, reject) => reject(reason)) }
测试
MyPromise.resolve("success=========").then(res => { console.log("res:", res) }) MyPromise.reject("error========").catch(err => { console.log("err:", err) })
网络异常,图片无法展示
|
all方法的设计
- 实现了上面的方法,对于all方法的实现就很容易了。主要弄清楚all方法的使用即可。
- 当传入的promises数组中全部promise状态都为fulfilled时,才会去调用Promise的resolve,并将其promises的结果放在数组中返回。
- 当传入的promises数组中有一个promise状态变为rejected时,就调用Promise的reject。
static all(promises) { return new MyPromise((resolve, reject) => { const values = [] promises.forEach(promise => { promise.then(res => { values.push(res) if (values.length === promises.length) { resolve(values) } }, err => { reject(err) }) }) }) }
测试
const p1 = new MyPromise((resolve) => { setTimeout(() => { resolve(1111) }, 1000) }) const p2 = new MyPromise((resolve, reject) => { setTimeout(() => { reject(2222) }, 2000) }) const p3 = new MyPromise((resolve) => { setTimeout(() => { resolve(3333) }, 3000) }) MyPromise.all([p1, p2, p3]).then(res => { console.log(res) }).catch(err => { console.log(err) })
网络异常,图片无法展示
|
allSettled方法的设计
- 这个方法主要是弥补all的缺陷的。
- 不管传入的promises中的promise状态变成什么,他都会放入数组,然后等到所有状态都改变后,调用Promise的resolve方法。
- 注意:他将传递该promise的状态和值。
static allSettled(promises) { return new MyPromise((resolve) => { const results = [] promises.forEach(promise => { promise.then(res => { results.push({ status: PROMISE_STATUS_FULFILLED, value: res}) if (results.length === promises.length) { resolve(results) } }, err => { results.push({ status: PROMISE_STATUS_REJECTED, value: err}) if (results.length === promises.length) { resolve(results) } }) }) }) }
测试
const p1 = new MyPromise((resolve) => { setTimeout(() => { resolve(1111) }, 1000) }) const p2 = new MyPromise((resolve, reject) => { setTimeout(() => { reject(2222) }, 2000) }) const p3 = new MyPromise((resolve) => { setTimeout(() => { resolve(3333) }, 3000) }) MyPromise.allSettled([p1, p2, p3]).then(res => { console.log(res) })
网络异常,图片无法展示
|
race方法的设计
- 这个方法主要是测试哪一个promise的状态先变化,就决定Promise的状态。
static race(promises) { return new HYPromise((resolve, reject) => { promises.forEach(promise => { // promise.then(res => { // resolve(res) // }, err => { // reject(err) // }) promise.then(resolve, reject) }) }) }
测试
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { reject(1111) }, 3000) }) const p2 = new MyPromise((resolve, reject) => { setTimeout(() => { reject(2222) }, 2000) }) const p3 = new MyPromise((resolve, reject) => { setTimeout(() => { reject(3333) }, 3000) }) MyPromise.race([p1, p2, p3]).then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
网络异常,图片无法展示
|
any方法的设计
- any方法会等到一个fulfilled状态,才会决定新Promise的状态。 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态。
- 如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
网络异常,图片无法展示
|
- 通过
err.errors
可以拿到全部的reject时传递的参数。
- 其实就是allSettled相反的思想。
static any(promises) { const reasons = [] return new HYPromise((resolve, reject) => { promises.forEach(promise => { promise.then(resolve, err => { reasons.push(err) if (reasons.length === promises.length) { reject(new AggregateError(reasons)) } }) }) }) }
测试
MyPromise.any([p1, p2, p3]).then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err.errors) })
网络异常,图片无法展示
|
以上就是手写Promise的所有思路,主要的实现还是搞清楚then方法的设计,搞定他,就搞定了Promise的90%了。其他方法就是理解使用就可以实现了。
珠峰架构公开课中的手写Promise
他的实现思路和coderwhy老师的不一样,我个人觉得他的思想思路比较容易理解。 主要设计
- resolvePromise主要是对then方法返回值的判断。
- then中的setTimeout是为了拿到then方法返回的Promise。
class Promise{ constructor(executor){ this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onResolvedCallbacks.forEach(fn=>fn()); } }; let reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; this.onRejectedCallbacks.forEach(fn=>fn()); } }; try{ executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled,onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }; let promise2 = new Promise((resolve, reject) => { if (this.state === 'fulfilled') { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'rejected') { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }; if (this.state === 'pending') { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0) }); }; }); return promise2; } } function resolvePromise(promise2, x, resolve, reject){ if(x === promise2){ return reject(new TypeError('Chaining cycle detected for promise')); } let called; if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then; if (typeof then === 'function') { then.call(x, y => { if(called)return; called = true; resolvePromise(promise2, y, resolve, reject); }, err => { if(called)return; called = true; reject(err); }) } else { resolve(x); } } catch (e) { if(called)return; called = true; reject(e); } } else { resolve(x); } }