目录
1.构造函数
promise构造函数接收一个回调函数作为参数,同时为该回调函数注入两个参数并立即调用该回调函数,传入参数分别是resolve,reject。这两个参数也是两个函数,调用之后就会对应的修改promise的状态与结果。
class MyPromise{
constructor(callback) {
// 为回调函数传入resolve和reject方法
const resolve = (result) => {
}
const reject = (result) => {
}
callback(resolve, reject)
}
}
2.状态与结果
promise构造函数中为回调函数所注入的两个参数可以修改promise状态,并将其所携带的参数作为结果保存下来。因为promise的状态改变是不可逆的,因此在修改状态前需要判断此时状态是否为pending,当状态为pending时才会执行修改状态逻辑。
const resolve = (result) => {
// 状态变为fufilled
if(this.state === "pending") {
this.state = "fufilled"
this.result = result
}
}
const reject = (result) => {
// 状态变为rejected
if(this.state === "pending") {
this.state = "rejected"
this.result = result
}
}
3.then方法
(1) .成功失败的回调
promise实例的then方法中接收两个参数,这两个参数均是回调函数,分别被命名为onFufilled,onRejected。这两个函数在promise的状态更改为对应的值时触发执行,并且会为其注入promise的结果作为参数。当调用then方法传入的参数不是函数时,会按照对应的处理逻辑将其包装成函数用以执行后续逻辑。
传入参数不是函数时的处理参考MDN文档:Promise.prototype.then() - JavaScript | MDN (mozilla.org)
then(onFufilled, onRejected) {
// 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
if(typeof onFufilled !== "function") {
onFufilled = (x) => x
}
if(typeof onRejected !== "function") {
onRejected = (x) => { throw(x) }
}
// 执行对应的回调
if(this.state === "fufilled") {
onFufilled(this.result)
} else if(this.state === "rejected") {
onRejected(this.result)
}
}
(2).异步与多次调用
当前then方法还存在一个问题,如果在promise的构造函数中执行了异步操作,比如定义一个定时器setTimeout,待到一秒钟之后再resolve抛出结果,此时then方法就无法接收到结果了。因此,我们需要在then方法中判断构造函数中是否为异步操作,如果是异步操作,则then方法调用时promise的状态一定还未改变。此时我们需要将回调函数是同一个执行队列保存起来,待到异步操作完成,再来执行对应的回调。这里我们使用一个对象数组来存储,因为对于promise对象实例来说,then方法是一个反复调用的(这里说的并非是链式调用,关于链式调用后续会解决),因此回调队列有可能不止一组的回调函数,所有用数组保存。(这里为了防止类外访问该回调队列,使用私有变量)
// then方法传入的回调函数队列
#handlers = [] // [{onFufilled, onRejected}...]
then(onFufilled, onRejected) {
// 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
if(typeof onFufilled !== "function") {
onFufilled = (x) => x
}
if(typeof onRejected !== "function") {
onRejected = (x) => { throw(x) }
}
// 执行对应的回调
if(this.state === "fufilled") {
onFufilled(this.result)
} else if(this.state === "rejected") {
onRejected(this.result)
} else if(this.state === "pending") {
// 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
this.#handlers.push({
onFufilled,
onRejected
})
}
}
上面代码已经将异步操作时需要执行的回调函数保存到队列当中,接下来需要在构造函数中的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)
})
}
}
4.异步任务
(1).核心API
原生的promise执行后本质上是一个异步任务,然而我们现在写的MyPromise还不支持异步任务。让我们来看一个例子:
console.log(1);
const p = new MyPromise((resolve, reject) => {
resolve(2)
})
p.then((res) => {
console.log(res)
})
console.log(3);
这段代码实际会输出 1,2,3,但我们期望的事是promise的执行是一个异步过程。因此我们需要在MyPromise类内部执行对应的回调时将其添加到异步队列来执行。这里就涉及到了如何将回调添加到异步队列。我们这里主要使用三种API来实现。
1.queueMicrotask: 这是一个全局函数,现代浏览器与node均支持该API,它主要是主动将一个任务推送到微任务队列等待调用。使用方式:
queueMicrotask(() => {
// 需要执行的异步任务
})
详情可见官网: queueMicrotask() - Web API | MDN (mozilla.org)
2. MutationObserver: 这是一个内置对象,用于创建一个观察器,可以通过观察器来观察dom元素的变化,用以执行相应的逻辑。观察到dom元素的变化后会将对应的执行逻辑推送到异步队列中等待调用。使用方式:
const obv = new MutationObserver(() => {
// 执行对应的逻辑
})
const div = document.createElement("div")
obv.observe(div, { attributes: true })
div.setAttribute("name", "123")
详情可见官网: MutationObserver - Web API | MDN (mozilla.org)
3.setTimeout : 由于浏览器等各方面的限制,如果以上两个api未能实现异步,则可使用setTimeout来实现异步操作。(所有浏览器及node全兼容)
(2).功能封装
这里我们封转一个私有方法,在then方法中需要调用回调的时候,我们使用该方法将回调函数推入异步队列等待调用,这样一来就可以实现异步任务了。这里使用了三种实现,主要是考虑到兼容问题。
#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)
}
}
then方法改动:
// 判断传入的两个参数是否为函数,如果不是函数,则按照对应的处理逻辑包装成函数
if(typeof onFufilled !== "function") {
onFufilled = (x) => x
}
if(typeof onRejected !== "function") {
onRejected = (x) => { throw(x) }
}
// 执行对应的回调
if(this.state === "fufilled") {
// 执行异步任务
this.#runAsyncTask(() => {
onFufilled(this.result)
})
} else if(this.state === "rejected") {
// 执行异步任务
this.#runAsyncTask(() => {
onRejected(this.result)
})
} else if(this.state === "pending") {
// 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
this.#handlers.push({
onFufilled: () => {
this.#runAsyncTask(() => {
onFufilled(this.result)
})
},
onRejected: () => {
this.#runAsyncTask(() => {
onRejected(this.result)
})
}
})
}
}
在为对调队列添加任务的时候,我们将onFufilled与onRejected也是用this.#runAsyncTask函数包装,待到真正调用的时候,就可以将其推入到异步队列当中。
5.链式调用
(1).成功状态
promise的then方法有个非常特殊的地方,这个方法可以链式的连续调用,本质上是由于then方法自身在执行完之后就会返回一个promise实例,实例上同样有then方法,这样就实现了连续调用。因此,在以上代码的基础上,我们需要在then方法中手动创建一个promise实例,最终将其返回。
同时,我们需要将此次then方法中onFufilled回调函数的返回结果作为参数传给新创建的promise的resolve函数中,如果上一次then方法中执行的逻辑报错,我们也需要将错误捕获,并通过reject函数传入新创建的promise中。
代码如下:
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)
resolve(res)
}
catch(e) {
reject(e)
}
})
} else if(this.state === "rejected") {
// 执行异步任务
this.#runAsyncTask(() => {
onRejected(this.result)
})
} else if(this.state === "pending") {
// 如果此时还是默认状态,则说明构造函数中执行了异步操作,此时需要将回调函数存起来,等到异步操作执行完毕后,再执行回调函数
this.#handlers.push({
onFufilled: () => {
this.#runAsyncTask(() => {
onFufilled(this.result)
})
},
onRejected: () => {
this.#runAsyncTask(() => {
onRejected(this.result)
})
}
})
}
})
// 方法结束返回新的promise实例
return newP
}
当上一个then方法中返回一个正常的结果时,上面的代码运行正常。但当返回一个promise对象时,以上的方法会直接将promise传入下一个then方法中,这不是我们所期望的,我们期望的是将promise的结果传入下一个then方法,因此我们需要在拿到onFulfilled返回值之后来判断是否是一个promise对象,如果是,则需要单独判断。
// 接收成功回调的返回值,作为下一个then的参数
const res = onFufilled(this.result)
// 如果返回值时promise实例,则需要单独处理
if(res instanceof MyPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
至此,then方法成功状态的链式调用完成,如下案例也可正常输出:2,3
const p = new MyPromise((resolve, reject) => {
resolve(2)
})
p.then((res) => {
console.log(res)
return new MyPromise((resolve, reject) => {
resolve(3)
})
},err => {
console.log(err)
}).then((data) => {
console.log(data)
},err=> {
console.log(err)
})
这里还存在一个小问题,原生的promise中,如果在then方法的onFufilled方法中返回了这个then方法 本身的返回值,js引擎就会报错,错误信息是:TypeError:Chaining cycle detected for promise #<Promise>,如下案例:
const p = new MyPromise((resolve, reject) => {
resolve(2)
})
const p2 = p.then((res) => {
return p2
},err => {
console.log(err)
})
要实现这一功能,我们就需要在获取到onFufilled的结果后与then方法将要返回的结果进行比较,如果两者相等,我们就需要手动抛出一个错误。
代码如下:
const res = onFufilled(this.result)
// 判断是否重复引用,如果重复引用则抛出错误
if(res === newP) {
throw new TypeError("Chaining cycle detected for promise #<Promise>")
}
(2).失败状态
then方法的onRejected回调里面的操作和onFufilled基本类似,因此我们将上面所写的代码封装成一个私有方法,直接复用即可。
#resolvePromise(promise,res,resolve,reject) {
// 判断是否重复引用,如果重复引用则抛出错误
if(res === promise) {
throw new TypeError("Chaining cycle detected for promise #<Promise>")
}
// 如果返回值时promise实例,则需要单独处理
if(res instanceof MyPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
}
else if(this.state === "rejected") {
// 执行异步任务
this.#runAsyncTask(() => {
try{
const res = onRejected(this.result)
this.#resolvePromise(newP, res, resolve, reject)
} catch(err) {
reject(err)
}
})
}
(3).默认状态
由于默认状态下我们需要将回调函数添加到任务队列当中,等到真正状态改变时在执行,所以我们需要在添加任务时同样做以上的处理,处理代码与成功失败状态完全一致,直接使用封装的私有方法。
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的基本功能算是实现了,但原生的promise还有catch,finally等方法,这些方法将会在下一篇文章中想解,由于文章篇幅与代码量的原因,现将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>")
}
// 如果返回值时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)
})
}
}
callback(resolve, reject)
}
// 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
}
}