「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
一、什么是promise
我们知道,在promise出现之前,我们采用回调的方式处理异步问题;这样当嵌套太深时容易陷入回调地狱,代码维护也会变的困难;promise的出现解决了此问题。
那什么是promise?
阮一峰老师在es6入门中说道:所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
二、手写实现
1. 青铜篇
Promises/A+中说道“promise”是一个对象或函数;下面代码 我们采用构造函数的方式实现。
我们先看一个promise简单使用
const promise = new Promise(function (resolve, reject) {
console.log('promise开始执行')
// throw new Error ('出错了')
// setTimeout(() => {
// resolve('成功')
// }, 0)
resolve('成功')
// reject('失败')
})
promise.then(
function (res) {
console.log('成功', res)
},
function (err) {
console.log('失败', err)
}
)
console.log('代码执行')
可以看到代码的运行结果是
- promise开始执行
- 代码执行
- 成功 成功
-
我们看出,首先promise 接受一个函数作为参数,我们叫他executor执行器,它是立即执行,里面有两个函数做参数,一个resolve(成功),一个reject(失败)。然后看到他有两个函数,一个去执行成功状态的函数,一个去执行失败状态的函数。
-
同时也注意到我们在执行器throw error时,他也执行了reject。 我们再看Promises/A+规范中说到, promise有三种状态 pending(等待), fulfilled(成功), rejected(失败)
-
等待态 可以转换到已完成或拒绝状态
-
成功态 不得过渡到任何其他状态;必须有一个不能改变的值。
-
失败态 不得过渡到任何其他状态;一定有理由,不能改变。
再看说的then方法:
- 其
then方法的行为符合本规范 promise必须提供一种then方法来访问其当前或最终的值或原因then方法接受两个参数
promise.then(onFulfilled, onRejected)
我们再会看上面的例子,我们在promise执行器执行resolve(成功),reject(失败)改变状态后,会在.then方法里面对应执行onFulfilled, onRejected,并且把成功的值和失败的理由作为参数进行了传递。
至此,我们可以实现最初级版的promise
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功态
const REJECTED = 'rejected' // 失败态
function MyPromise(excuter) {
this.status = PENDING
this.value = undefined // 成功的结果
this.reason = undefined // 失败的原因
const resolve = (value) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.value = value
this.status = FULFILLED
}
}
const reject = (reason) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.reason = reason
this.status = REJECTED
}
}
try {
excuter(resolve, reject) // 立即执行
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) { // then方法挂在到原型上
// console.log(this) // 注意这里的this
if (this.status === FULFILLED) {
onFulfilled(this.value) // 成功后执行onFulfilled 并把成功的结果作为参数
}
if (this.status === REJECTED) {
onRejected(this.reason) // onRejected 并把成功的结果作为参数
}
}
2. 白银篇
现在我们升级打怪到达白银段位,我们首先看一个基于我们青铜段位实现的promise来执行的例子。
const promise = new MyPromise(function (resolve, reject) {
console.log('promise开始执行')
setTimeout(() => {
resolve('成功')
}, 0)
})
promise.then(
function (res) {
console.log('成功', res)
},
function (err) {
console.log('失败', err)
}
)
console.log('代码执行')
然后我看看运行结果:
ok,我们发现then方法里面都没有执行成功的回调。同时,我们在then方法里面打印this.status,我们发现此时的状态是pending(等待态)。
现在我们知道了原因就是setTimeout 包裹的resolve是异步执行的,当走到then方法里面时,状态没有改变; 外加可能同时有好几个promise在执行。于是,我们采用类似依赖收集的方式,在pending状态时把成功或者失败的回调收集起来,如何在状态改变的时候去循环执行它们。
function MyPromise(excuter) {
this.status = PENDING
this.value = undefined // 成功的结果
this.reason = undefined // 失败的原因
+ this.onResolvedCallbacks = [] // 存放成功的回调
+ this.onRejectedCallbacks = [] // 存放失败的回调
const resolve = (value) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.value = value
this.status = FULFILLED
+ this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.reason = reason
this.status = REJECTED
+ this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
excuter(resolve, reject) // 立即执行
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) { // then方法挂在到原型上
// console.log(this) // 注意这里的this
+ // console.log(11122, this.status)
if (this.status === FULFILLED) {
onFulfilled(this.value) // 成功后执行onFulfilled 并把成功的结果作为参数
}
if (this.status === REJECTED) {
onRejected(this.reason) // onRejected 并把成功的结果作为参数
}
+ if (this.status === PENDING) {
+ this.onResolvedCallbacks.push(() => onFulfilled(this.value))
+ this.onRejectedCallbacks.push(() => onRejected(this.reason))
+ }
}
接下来,我们来实现then的链式调用。
首先我们看看Promises/A+规范是怎么说的
-
这两个
onFulfilled和onRejected可选的参数:- 如果
onFulfilled不是函数,则必须忽略它。 - 如果
onRejected不是函数,则必须忽略它。
这里我们得到一个优化点,就是在我们的then方法里面要判断
onFulfilled和onRejected的类型,这个优化点我们先记录下来 后面一起实现,我们再往下看看讲了什么。 - 如果
-
then可以在同一个 Promise 上多次调用。- 如果promise 成功了,所有相应的
onFulfilled回调必须以他们注册时的顺序依次执行. - 如果promise被拒绝,所有相应的
onRejected回调必须以他们注册时的顺序依次执行.
- 如果promise 成功了,所有相应的
-
then必须返回一个promise
promise2 = promise1.then(onFulfilled, onRejected);
这样我们知道在then方法里面我们需要再return一个promise出去,这样我们才能做到链式调用
MyPromise.prototype.then = function (onFulfilled, onRejected) {
...
promise2 = new MyPromise((resolve, reject)=>{
})
return promise2
}
- 3.1 如果其中一个
onFulfilled或onRejected返回一个值x,则运行 Promise Resolution Procedure[[Resolve]](promise2, x)。 - 3.2 如果其中一个
onFulfilled或onRejected抛出异常e,则promise2必须以拒绝e为理由。 - 3.3 如果
onFulfilled不是一个函数并且promise1被满足,则promise2必须以与 相同的值来满足promise1。 - 3.4 如果
onRejected不是函数并且promise1被拒绝,则promise2必须以与 相同的原因被拒绝promise1。
针对于3.1、3.2我们结合一个例子来看
const promise = new Promise(function (resolve, reject) {
console.log('promise开始执行')
setTimeout(() => {
resolve('成功')
}, 0)
// resolve('成功')
// reject('失败')
})
promise.then(
function (res) {
console.log('成功', res)
return 1
},
function (err) {
console.log('失败', err)
return '失败了哦'
}
).then((data) => {
console.log(3333, data)
}, (data) => {
console.log(2222, data)
})
console.log('代码执行')
运行结果
注意:这里的promise不是我们自己的
- 我们可以看到
promise.then里面return的值在第二个then里面的onFulfilled/onRejected里面作为参数拿到了 那它是怎么拿到的呢 - 我们看第一个
.then里面的参数是如何拿到的。我们自然知道,在new Promise里面我们执行了resolve('成功')/reject('失败'),然后我们在第一个.then里面的onFulfilled/onRejected拿到了传递过来的参数,这样我们就知道该如何下手,就是在我们return的promise2里面执行resolve/reject。 但是问题又来了,参数呢,参数怎么办
我们再观察,promise第一个.then里面onFulfilled/onRejected有return,自然想到我们在MyPromise.prototype.then里面onFulfilled(this.value)/onRejected(this.reason) 可以拿到返回值,我们赋值给一个变量x。那我们怎么拿到这个变量x,我们知道new Promise里面的代码是立即执行的,于是我们把三个if判断放到promise2里面去执行,这样就可以拿到x了,完美~
还没有结束,我们再看看,上面说的3.3,3.4
我们先结合本来的promise实现的例子来看这两点的意思
const promise = new MyPromise(function (resolve, reject) {
console.log('promise开始执行')
resolve('成功')
// reject('失败')
})
promise.then(
'成功', '失败'
).then((data) => {
console.log(3333, data)
}, (data) => {
console.log(2222, data)
})
console.log('代码执行')
运行结果
我们看到promise.then里面的参数不是两个函数了,正如规范里面所说的那样,onFulfilled/onRejected不是一个函数,并且promise1的状态被改变fulfilled/rejected,promise2必改变为和peomise1一样的状态,并且把它的值作为参数。就如同上面执行结果的console.log(2222, '成功')
ok,到这里我们上代码
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功态
const REJECTED = 'rejected' // 失败态
function MyPromise(excuter) {
this.status = PENDING
this.value = undefined // 成功的结果
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = [] // 存放成功的回调
this.onRejectedCallbacks = [] // 存放失败的回调
const resolve = (value) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.value = value
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
excuter(resolve, reject) // 立即执行
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) { // then方法挂在到原型上
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
promise2 = new MyPromise((resolve, reject)=>{
if (this.status === FULFILLED) {
let x = onFulfilled(this.value) // 成功后执行onFulfilled 并把成功的结果作为参数
resolve(x)
}
if (this.status === REJECTED) {
let x = onRejected(this.reason) // onRejected 并把成功的结果作为参数
resolve(x)
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
let x = onFulfilled(this.value)
resolve(x)
})
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason)
resolve(x)
})
}
})
return promise2
}
好了,虽然这里面还有不少问题,我们白银阶段先到这里~
3. 黄金篇
在白银篇中,我们对于x的值,只考虑了普通值的处理方式;x是普通值的时候我们直接传递给下一个then。
我们再看规范中说的,如果x是一个promise,采用它的状态:
- 如果
x是pending状态,则promise必须保持待处理状态,直到xpending 状态转为 fulfilled 或 rejected 状态 - 如果/当
x状态是 fulfilled,resolve 它,并且传入和 promise1 一样的值 value - 如果/当
x状态是 rejected,reject 它,并且传入和 promise1 一样的值 reason
在此我们知道,x是一个promise的时候,我们必须等到x返回的结果来判断promise2是成功还是失败。
于是我们抽离一个公共的方法去处理
resolvePromise(promise2, x, resolve, reject)
但是对于resolvePromise的调用的地方我们需要用setTimeout和try catch,看下下图中的注释说明
上面我们看了x是promise的处理方法,我们再看规范中说到:如果promise和x引用同一个对象,promise则以 aTypeError为理由拒绝。
我们看下例子
const p1 = new Promise((resolve, reject) => {
resolve('成功')
})
const p2 = p1.then(data => {
return p2
})
p2.then(data => {}, err => {
console.log(2222, err)
})
执行结果
所以我们知道在
resolvePromise中首先要判断x和promise2是不是相等,相等要抛TypeError错。
该阶段我们实现promise规范中2.3 promise解决程序,对于x是函数或对象的实现我们下一篇再说,先上一波代码
const PENDING = 'pending' // 等待
const FULFILLED = 'fulfilled' // 成功态
const REJECTED = 'rejected' // 失败态
function MyPromise(excuter) {
this.status = PENDING
this.value = undefined // 成功的结果
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = [] // 存放成功的回调
this.onRejectedCallbacks = [] // 存放失败的回调
const resolve = (value) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.value = value
this.status = FULFILLED
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = (reason) => {
if (this.status === PENDING) { // 状态一经改变 不能再改变
this.reason = reason
this.status = REJECTED
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
excuter(resolve, reject) // 立即执行
} catch (error) {
reject(error)
}
}
const resolvePromise = (promise2, x, resolve, reject) => {
// 如果promise和x引用同一个对象,promise则以TypeError为理由拒绝。
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 如果x是一个对象或函数的处理
} else {
// 如果 x 即不是函数类型也不是对象类型,直接 resolve x(resolve(x))
resolve(x)
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) { // then方法挂在到原型上
// console.log(this) // 注意这里的this
// console.log(11122, this.status)
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) {
// 此时变成异步之后我们try包裹的excuter不能catch到错误,所以我们的这里的代码得用try catch去报错,抛错的时候在catch中执行reject
setTimeout(() => {
try {
let x = onFulfilled(this.value) // 成功后执行onFulfilled 并把成功的结果作为参数
// 此时我们直接拿promise2是拿不到的会报错,我们必须等这个new完之后才能拿到promise2,我们在这里加一个setTimeout来处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason) // onRejected 并把成功的结果作为参数
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0)
})
}
})
return promise2
}
4. 铂金篇
接着上面的,我们考虑x是一个对象或函数的情况。
我们还是看规范中所说的:
- 设置一个
then作为x.then。- 如果检索属性
x.then导致抛出了一个异常e,用e作为原因拒绝promise - 如果
then是一个函数,用x作为this调用它。then方法的参数为俩个回调函数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise: -
- 如果
resolvePromise用一个值y调用,运行[[Resolve]](promise, y)。译者注:这里再次调用[[Resolve]](promise,y),因为y可能还是promise - 如果
rejectPromise用一个原因r调用,用r拒绝promise。译者注:这里如果r为promise的话,依旧会直接reject,拒绝的原因就是promise。并不会等到promise被解决或拒绝 - 2.3.3.3.3. 如果
resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。译者注:这里主要针对thenable,promise的状态一旦更改就不会再改变。 - 2.3.3.3.4. 如果调用
then抛出了一个异常e, -
- 2.3.3.4.1. 如果
resolvePromise或rejectPromise已经被调用,忽略它 - 2.3.3.4.2. 否则,用
e作为原因拒绝promise
- 2.3.3.4.1. 如果
- 如果
- 如果检索属性
- 如果
then不是一个函数,用x解决promise
好,我们这里上代码看resolvePromise完整的实现
const resolvePromise = (promise2, x, resolve, reject) => {
// 如果promise和x引用同一个对象,promise则以TypeError为理由拒绝。
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
let called; // 2.3.3.3.3. 如果resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,那么第一次调用优先,以后的调用都会被忽略。
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// 如果x是一个对象或函数的处理
try { // 2.3.3.2 如果检索属性x.then导致抛出了一个异常e,用e作为原因拒绝promise
let then = x.then // 2.3.3.1 让then成为x.then
if (typeof then === 'function') {
then.call(x, y => { // 如果then是一个函数,用x作为this调用它
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 {
// 如果 x 即不是函数类型也不是对象类型,直接 resolve x(resolve(x))
resolve(x)
}
}
好了,至此我们的手写简易Promise已经完成了,恭喜大家修炼完成~