原生JS实现Promise
摘要:本文讲述如何使用原生js实现Promise。先跟着文章的思路来实现,然后尝试自己实现两三次就会很通顺了。
1、创建Promise对象的时候需要使用new 关键字,所以Promise就是一个类,通过new可以创建Promise实例
const p = new Promise((resolve, reject) => {
resolve('成功啦哈哈哈')
})
class MyPromise {
}
2、new Promise时需要一个函数作为参数,这个函数是立即执行的,我们称之为执行器,所以要在类中使用constructor接收这个执行器,并且立即执行
class MyPromise {
constructor(executor) {
executor()
}
}
3、执行器需要接受两个参数,一个resolve方法,一个reject方法,这两个方法是用来修改Promise对象的状态status的,所以首先,需要维护一个status属性,将这三种状态定义为常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
executor()
}
status = PENDING
}
4、定义resolve方法和reject方法来修改status属性,并且传递给执行器
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING
resolve() {
this.status = FULFILLED
}
reject() {
this.status = REJECTED
}
}
5、我们已经实现了用resolve和reject方法修改对象的状态。Promise对象都有一个then方法,在这个then方法中,会传递两个回调函数,第一个是Promise对象的状态为fulfilled的回调,第二个是Promise对象的状态为rejected的回调。所以在then中我们会根据status属性来决定执行哪一个回调函数。
then(successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback()
} else if (this.status === REJECTED) {
failCallback()
}
}
6、successCallback和failCallback都是需要接收参数的,successCallback接受的参数就是resolve传进来的参数,所以resolve方法需要维护一个value,failCallback接收的参数就是reject传进来的参数,表示失败的原因,所以reject方法需要维护一个reason
value = undefined
reason = undefined
resolve(value) {
this.status = FULFILLED
this.value = value
}
reject(reason) {
this.status = REJECTED
this.reason = reason
}
then(successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
}
}
7、如果Promise的执行器中的代码是异步的,就意味着代码走到then的时候,resolve方法和reject方法还没执行,此时的status还是pending ,此时需要将successCallback和failCallback保存下来,等到resolve或者reject方法执行之后再执行成功或者失败回调
successCallback = undefined
failCallback = undefined
resolve(value) {
this.status = FULFILLED
this.value = value
if (this.successCallback) {
this.successCallback(this.value)
}
}
reject() {
this.status = REJECTED
this.reason = reason
if (this.failCallback) {
this.failCallback(this.reason)
}
}
then(successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else if (this.status === PENDING) {
this.successCallback = successCallback
this.failCallback = failCallback
}
}
8、Promise对象的状态一旦确定就不能被改变,所以在resolve和reject中判断status,如果不是pending直接return
resolve(value) {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
if (this.successCallback) {
this.successCallback(this.value)
}
}
reject() {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
if (this.failCallback) {
this.failCallback(this.reason)
}
}
9、到现在已经实现了then方法,并且支持异步调用的情况,测试一下吧,测试代码:
const p = new MyPromise((resolve, reject) => {
setTimeout(function () {
resolve(999)
}, 3000)
setTimeout(function () {
reject(999)
}, 3000)
})
p.then(value => {
console.log('成功' + value);
}, reason => {
console.log('失败' + reason);
})
嗷,出现了报错呢。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uC8iqaqp-1677584696285)(https://tcs.teambition.net/storage/312r85e134be88510cdcda2f334f89e4a85e?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IjVhMTEyNTljNmFkZTcyNDIyNDMyZGNkOCIsImV4cCI6MTY3NzU1MTEzMiwiaWF0IjoxNjc3NTQ3NTMyLCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMnI4NWUxMzRiZTg4NTEwY2RjZGEyZjMzNGY4OWU0YTg1ZSJ9.GHr3ZiJX8McZYj06uLORxjb96FPd3wLbuwYbKPKgM18&download=image.png “”)]
原因是resolve的this指向问题,看一下resolve方法的调用,相当于在executor里传递了两个function,executor中的上下文是当前对象,但是在function中的this指向的是undefined(es6中的class是严格模式,所以指向undifined,如果是非严格模式,指向的是window)
executor(function () {
console.log(this);
}, function () {
})
所以应该将resolve和reject写成箭头函数的形式
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
if (this.successCallback) {
this.successCallback(this.value)
}
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
if (this.failCallback) {
this.failCallback(this.reason)
}
}
10、测试成功,继续往下进行。then是可以实现多次调用的
const p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('第一个成功啦')
}, 1000)
})
p1.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
p1.then(value => {
console.log(value);
}, reason => {
console.log(reason);
})
在异步的情况下,这些回调函数需要全部被保存起来,等到resolve或者reject改变状态后依次全部调用。所以this.successCallback和failCallback要写成数组的形式
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length > 0) {
this.successCallback.shift()(this.value)
}
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length > 0) {
this.failCallback.shift()(this.reason)
}
}
then(successCallback, failCallback) {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else if (this.status === PENDING) {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
}
11、Promise对象可以实现链式调用,所以then方法中返回的应该是一个Promise。如果上一个then中返回的不是Promise,需要将返回值传递给下一个then的成功回调,如果上一个then中返回的是Promise,需要将这个Promise的状态和返回值传递给下一个then的回调
(1)首先实现返回一个Promise
then(successCallback, failCallback) {
const p2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
successCallback(this.value)
} else if (this.status === REJECTED) {
failCallback(this.reason)
} else if (this.status === PENDING) {
this.successCallback.push(successCallback)
this.failCallback.push(failCallback)
}
})
return p2
}
(2)this.status === FULFILLED时,需要先拿到回调函数的返回值,然后判断这个返回值是不是MyPromise类型。如果时MyPromise类型,需要执行它的then方法,根据其状态确定接下来的then调用哪一个回调函数。
if (this.status === FULFILLED) {
const x = successCallback(this.value)
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
const p = new MyPromise((resolve, reject) => {
resolve(888)
})
//第一个then将this.value改为888,并且执行成功回调
p.then(value => {
console.log('第一次成功' + value);
return new MyPromise((resolve, reject) => {
//这里会将this.value改成777,将this.status改为fulfilled,但没有执行回调
resolve(777)
})
}, reason => {
console.log('失败' + reason);
}).then(value => {
//拿到value:777并执行回调
console.log('第二次成功' + value);
}, reason => {
console.log('第二次失败' + reason);
})
(3)在this.status === REJECTED和PENDING时,执行的操作是类似的步骤,所以将相同的操作抽离出来:
//解析MyPromise
function resolvePromise(x, resolve, reject) {
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
(4)当状态是pending的时候,之前是直接将回调函数push进相应的数组中,但是现在我们要push一个新的函数,需要更多的操作,这个函数不需要参数,在执行的时候会找执行时的this.value和this.reason
if (this.status === PENDING) {
this.successCallback.push(() => {
const x = successCallback(this.value)
resolvePromise(x, resolve, reject)
})
this.failCallback.push(() => {
const x = failCallback(this.reason)
resolvePromise(x, resolve, reject)
})
}
(5)在执行successCallback数组的时候就不需要再传递参数了
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length > 0) {
this.successCallback.shift()()
}
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length > 0) {
this.failCallback.shift()()
}
}
12、现在我们已经实现了then方法的链式调用,测试一下吧~ 测试代码:
const p = new MyPromise((resolve, reject) => {
setTimeout(function () {
resolve(888)
}, 3000)
})
p.then(value => {
console.log('第一次成功' + value);
return new MyPromise((resolve, reject) => {
setTimeout(function () {
reject(777)
}, 1000)
})
}, reason => {
console.log('失败' + reason);
}).then(value => {
console.log('第二次成功' + value);
}, reason => {
console.log('第二次失败' + reason);
})
13、测试通过啦!不过还有一个小问题,看下面一段代码:
const p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('第一个成功啦')
}, 1000)
})
const p2 = p1.then(value => {
console.log(value);
return p2
}, reason => {
console.log(reason);
})
在then中返回了then的结果,控制台会报错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBwO2Fym-1677584696287)(https://tcs.teambition.net/storage/312rd975a185108c6383bf3ada4da2ab76ad?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IjVhMTEyNTljNmFkZTcyNDIyNDMyZGNkOCIsImV4cCI6MTY3NzU2MDI5MiwiaWF0IjoxNjc3NTU2NjkyLCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMnJkOTc1YTE4NTEwOGM2MzgzYmYzYWRhNGRhMmFiNzZhZCJ9.wFnmgkeeq5lpd7tdgz-_YBgIL2FLPyYo8koFgeCDa8I&download=image.png “”)]
在我们的代码中也需要实现一下这个控制哦
我们在then方法中返回了一个MyPromise对象,并且在这里我们可以拿到成功回调的return值,所以需要判断这两个MyPromise对象是否相等
then(successCallback, failCallback) {
const p2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
const x = successCallback(this.value)
resolvePromise(p2, x, resolve, reject)
} else if (this.status === REJECTED) {
const x = failCallback(this.reason)
resolvePromise(p2, x, resolve, reject)
} else if (this.status === PENDING) {
this.successCallback.push(() => {
const x = successCallback(this.value)
resolvePromise(p2, x, resolve, reject)
})
this.failCallback.push(() => {
const x = failCallback(this.reason)
resolvePromise(p2, x, resolve, reject)
})
}
})
return p2
}
//解析MyPromise
function resolvePromise(p2, x, resolve, reject) {
if (p2 == x) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
return
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
14、实现Promise的catch方法,catch方法只处理reject的情况,其他的和then方法是一样的,所以直接在catch中调用then方法就可以,并且then和catch都可以支持不传递参数,不传回调函数的时候,相当于要把当前对象的状态向下传递,所以直接返回this
catch(failCallback) {
return this.then(undefined, failCallback)
}
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => this
failCallback = failCallback ? failCallback : reason => this
...
}
15、出现报错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhKZd6Lz-1677584696288)(https://tcs.teambition.net/storage/312r0885c3f4b3aefe20dd5f864cac6ba5f0?Signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBcHBJRCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9hcHBJZCI6IjU5Mzc3MGZmODM5NjMyMDAyZTAzNThmMSIsIl9vcmdhbml6YXRpb25JZCI6IjVhMTEyNTljNmFkZTcyNDIyNDMyZGNkOCIsImV4cCI6MTY3NzU4NDk0OCwiaWF0IjoxNjc3NTgxMzQ4LCJyZXNvdXJjZSI6Ii9zdG9yYWdlLzMxMnIwODg1YzNmNGIzYWVmZTIwZGQ1Zjg2NGNhYzZiYTVmMCJ9.a-SWU6XMXUNL_xjbRdJDp2V3rcKNQ1Gb1qXM2IqT-WE&download=image.png “”)]
原因是我们在new p2的过程中使用了p2,所以then内部对于p2的使用要放在异步代码中
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => this
failCallback = failCallback ? failCallback : reason => this
const p2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
const x = successCallback(this.value)
resolvePromise(p2, x, resolve, reject)
})
} else if (this.status === REJECTED) {
setTimeout(() => {
const x = failCallback(this.reason)
resolvePromise(p2, x, resolve, reject)
})
} else if (this.status === PENDING) {
setTimeout(() => {
this.successCallback.push(() => {
const x = successCallback(this.value)
resolvePromise(p2, x, resolve, reject)
})
this.failCallback.push(() => {
const x = failCallback(this.reason)
resolvePromise(p2, x, resolve, reject)
})
})
}
})
return p2
}
16、下面我们实现Promise.resolve方法,这是一个类上的静态方法,可以接收普通数据或者Promise对象,如果是普通数据可以转化为Promise对象并且resolve这个数据,如果是Promise对象不做任何操作
static resolve(item) {
if (item instanceof MyPromise) {
return item
} else {
return new MyPromise((resolve, reject) => {
resolve(item)
})
}
}
17、实现Promise对象的finally方法,finally方法是不管Promise对象的状态是成功还是失败都会调用的。finally不接受参数,不影响状态,返回的数据不会被后边的then捕捉到。所以直接执行,并且返回当前Promise对象
finally(callBack) {
callBack()
return this
}
18、实现Promise.all这个静态方法,这个方法接收一个数组作为参数,返回的是一个Primose,并且resolve所有元素处理结果组成的数组,如果元素是普通数据,则处理结果是自身,如果是Promise对象,则处理结果就是resolve的参数,如果有一个元素reject,那么返回的Promise对象的状态就是失败
static all(arr) {
return new MyPromise((resolve, reject) => {
let res = [];
let index = 0;
function setData(key, value) {
res[key] = value
index++
if (index == arr.length) resolve(res)
}
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (item instanceof MyPromise) {
item.then(value => {
setData(i, value)
}, reason => {
reject(reason)
})
} else {
setData(i, item)
}
}
})
}
到这里大体就实现完毕啦!!只是还有一点没有实现,就是如果Promise对象的状态为失败的时候,如果没有被catch捕捉就会报错,但是如果有catch或者失败回调就不会报错 这个功能各位看官看一看能不能实现
所有代码:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
executor(this.resolve, this.reject)
}
status = PENDING
value = undefined
reason = undefined
successCallback = []
failCallback = []
resolve = value => {
if (this.status !== PENDING) return
this.status = FULFILLED
this.value = value
while (this.successCallback.length > 0) {
this.successCallback.shift()()
}
}
reject = reason => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while (this.failCallback.length > 0) {
this.failCallback.shift()()
}
}
then(successCallback, failCallback) {
successCallback = successCallback ? successCallback : value => this
failCallback = failCallback ? failCallback : reason => this
const p2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
const x = successCallback(this.value)
resolvePromise(p2, x, resolve, reject)
})
} else if (this.status === REJECTED) {
setTimeout(() => {
const x = failCallback(this.reason)
resolvePromise(p2, x, resolve, reject)
})
} else if (this.status === PENDING) {
setTimeout(() => {
this.successCallback.push(() => {
const x = successCallback(this.value)
resolvePromise(p2, x, resolve, reject)
})
this.failCallback.push(() => {
const x = failCallback(this.reason)
resolvePromise(p2, x, resolve, reject)
})
})
}
})
return p2
}
catch(failCallback) {
return this.then(undefined, failCallback)
}
static resolve(item) {
if (item instanceof MyPromise) {
return item
} else {
return new MyPromise((resolve, reject) => {
resolve(item)
})
}
}
finally(callBack) {
callBack()
return this
}
static all(arr) {
return new MyPromise((resolve, reject) => {
let res = [];
let index = 0;
function setData(key, value) {
res[key] = value
index++
if (index == arr.length) resolve(res)
}
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (item instanceof MyPromise) {
item.then(value => {
setData(i, value)
}, reason => {
reject(reason)
})
} else {
setData(i, item)
}
}
})
}
}
//解析MyPromise
function resolvePromise(p2, x, resolve, reject) {
if (p2 == x) {
throw new TypeError('Chaining cycle detected for promise #<Promise>')
return
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}