关乎手写Promise,网上已经有好多相关资料,我也查阅过相关资料,但大多不够全,并且有些没那么易懂,所以自己想从头写个通俗易懂的Promise。
查看完整代码请戳:手写Promise
从零开始,手写 Promise
原生Promise
首先我们看下原先Promise大概有什么功能:
在控制台打印下
console.log(new Promise(
function(resolve,reject){ })
);
展开结果如下:
从控制台输出的Promise对象我们可以清楚的看到Promise对象有以下几种基本方法:
Promise.resolve()
Promise.reject()
Promise.all()
Promise.race()
Promise.prototype.then()
Promise.prototype.catch()
Promise.prototype.finally()
- then,catch,finally这三个属于实例方法。
- reject,resolve,all,race,这三个属于构造函数里的方法,是静态方法。
Promise/A+
我们想要手写一个 Promise,就要遵循 Promise/A+ 规范,业界所有 Promise 的类库,像是ES6的Promise也好,jQuery的Promise也好都遵循这个规范。
开始手写
一、Promise核心逻辑实现
const promise = new Promise((resolve, reject) => {
resolve('success')
reject('err')
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
分析下Promise的基本特征:
- Promise 是一个类,在执行这个类的时候会传入一个执行器(executor),这个执行器会立即执行
- executor接受两个参数,分别是resolve和reject
- Promise 会有三种状态,默认状态是pending 「规范 Promise/A+ 2.1」
- pending 等待
- fulfilled 完成
- rejected 失败- promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」
- promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」
- 状态只能由 pending --> fulfilled 或者 pending --> rejected,且一但发生改变便不可二次修改。
- Promise 中使用 resolve 和 reject 两个函数来更改状态
- then 方法内部做的事情就是状态判断,接收两个参数,分别是Promise成功的回调onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」
- 如果状态是成功,调用成功回调函数(onFulfilled),参数是Promise的value;
- 如果状态是失败,调用失败回调函数(onRejected),参数是Promise的reason;
代码实现:
-
新建 CustomPromise 类,传入执行器 executor
// 新建 CustomPromise.js // 新建 CustomPromise 类 class CustomPromise { constructor(executor){ // executor 是一个执行器,进入会立即执行 executor() } }
-
executor 传入 resolve 和 reject 方法作为参数,这样使用方就可以接收到这两个方法并调用
// CustomPromise.js
// 新建 CustomPromise 类
class CustomPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 更改成功后的状态
resolve = () => {}
// 更改失败后的状态
reject = () => {}
}
- 状态与结果的管理
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 CustomPromise 类
class CustomPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
// 状态为 PENDING 时才可以更新状态
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
}
}
// 更改失败后的状态
reject = (reason) => {
// 状态为 PENDING 时才可以更新状态
if (this.status === PENDING) {
// 状态修改为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
}
}
}
- then 的简单实现
// 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
- 使用 module.exports 对外暴露 CustomPromise 类
// CustomPromise.js
module.exports = CustomPromise;
完整代码:
// CustomPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// 新建 CustomPromise 类
class CustomPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING
// 成功之后的值
value = null
// 失败之后的原因
reason = null
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 更改成功后的状态
resolve = (value) => {
// 状态为 PENDING 时才可以更新状态
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED
// 保存成功之后的值
this.value = value
}
}
// 更改失败后的状态
reject = (reason) => {
// 状态为 PENDING 时才可以更新状态
if (this.status === PENDING) {
// 状态修改为失败
this.status = REJECTED
// 保存失败后的原因
this.reason = reason
}
}
}
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason)
}
}
module.exports = CustomPromise
验证下:
// 新建 testCustomPromise.js
// 引入我们的 CustomPromise.js
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
resolve('ok')
reject('error')
})
p.then(
(value) => {
console.info('resolve', value)
},
(err) => {
console.info('reject', err)
}
)
// 执行结果:resolve ok
执行结果符合我们的预期,第一步完成了,掌声响起来👏👏👏
二、在 Promise 类中支持异步逻辑
接下来的功能都采用测试驱动功能的完善
给测试用例加上异步逻辑,例如:
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
reject('error')
}, 1000)
})
p.then(
(value) => {
console.info('resolve', value)
},
(err) => {
console.info('reject', err)
}
)
// 没有打印信息
分析原因:
主线程代码开始执行,传感器(executor)会立即执行,但由于setTimeout是宏任务,并不会立即执行,也就不会更改status的状态为fulfilled;then会立即执行,此时状态为pending,不会执行成功(onFulfilled)或失败(onRejected)回调函数。
完善功能:
所以需要在then函数里处理下pending状态,改造如下:
- 缓存成功或失败回调
// 存储成功回调函数
onFulfilledCallback = null
// 存储失败回调函数
onRejectedCallback = null
- then方法中对pending状态的处理,将回调函数存储
if (this.status === PENDING) {
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到promise状态切为成功或失败时再执行
this.onFulfilledCallback = onFulfilled
this.onRejectedCallback = onRejected
}
- resolve 与 reject 中调用回调函数
// 更改成功后的状态
resolve = (value) => {
// 状态为 PENDING 时才可以更新状态
if (this.status === PENDING) {
// 省略其它...
// 判断成功回调是否存在,如果存在就调用
this.onFulfilledCallback && this.onFulfilledCallback(value)
}
}
// 更改失败后的状态
reject = (reason) => {
// 状态为 PENDING 时才可以更新状态
if (this.status === PENDING) {
// 省略其它...
// 判断失败回调是否存在,如果存在就调用
this.onRejectedCallback && this.onRejectedCallback(reason)
}
}
这其实就是一个发布订阅模式,先将then里面的两个函数储存起来,在resovle与reject里取出函数,并执行函数。这种收集依赖->触发通知->取出依赖执行的方式,被广泛运用于发布订阅模式的实现。
验证:
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
reject('error')
}, 1000)
})
p.then(
(value) => {
console.info('resolve', value)
},
(err) => {
console.info('reject', err)
}
)
// 等待2秒,弹出resolve ok
三、实现 then 方法多次调用,添加多个处理函数
同样的先看下添加多个then的测试脚本
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
reject('error')
}, 1000)
})
p.then((value) => {
console.info(1)
console.info('resolve', value)
})
p.then((value) => {
console.info(2)
console.info('resolve', value)
})
p.then((value) => {
console.info(3)
console.info('resolve', value)
})
// 3
// resolve ok
现象:
只输出最后一次then函数的结果:3 resolve ok
分析原因:
因为如果是同步回调,那现状的then会直接返回当前的值,不会有问题;但当为异步回调(像上面的setTimeout)时,现如今的then里会将回调函数存储在一个变量里,后面的变量值会覆盖前面的,就会出现只有最后一个then函数的onFulfilled或onRejected得到调用
完善功能:
将then中的回调函数存储在一个数组里,然后在状态切为resolve或rejected时遍历执行
-
类中新增两个数组,去掉原先的回调函数变量
// 存储成功回调函数 // onFulfilledCallback = null onFulfilledCallbacks = [] // 存储失败回调函数 // onRejectedCallback = null onRejectedCallbacks = []
-
在then函数中,将回调函数存入数组中
if (this.status === PENDING) { // 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来 // 等到promise状态切为成功或失败时再执行 // this.onFulfilledCallback = onFulfilled // this.onRejectedCallback = onRejected this.onFulfilledCallbacks.push(onFulfilled) this.onRejectedCallbacks.push(onRejected) }
-
在resolve中遍历调用成功回调、在reject中遍历调用失败回调
// resolve里面将所有成功的回调拿出来执行
if (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.forEach((fn) => fn(value))
}
// reject里面将所有成功的回调拿出来执行
if (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.forEach((reason) => fn(reason))
}
验证:
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 1000)
})
p.then((value) => {
console.info(1)
console.info('resolve', value)
})
p.then((value) => {
console.info(2)
console.info('resolve', value)
})
p.then((value) => {
console.info(3)
console.info('resolve', value)
})
// 1
// resolve success
// 2
// resolve success
// 3
// resolve success
四、实现then方法的链式调用
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 1000)
})
function other() {
return new CustomPromise((resolve, reject) => {
resolve('success')
})
}
p.then((value) => {
console.info(1)
console.info('resolve', value)
return other()
}).then((value) => {
console.info(2)
console.info('resolve', value)
})
// }).then((value) => {
^
// TypeError: Cannot read property 'then' of undefined
现象:
报错,TypeError: Cannot read property ‘then’ of undefined
分析原因:
then 是 CustomPromise 实例才能调用的方法,所以要求then函数本身要返回个 CustomePromise 对象,这样才能实现链式调用。另外then方法里 return 一个返回值作为下一个 then 方法的参数,如果是 return 一个 CustomePromise 对象,那么就需要判断它的状态,要等 return 里的 CustomPromise 状态切换了,才继续执行。
功能完善:
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
// 为了实现链式调用,创建个新的 CustomPromise 对象,并返回
const promise2 = new CustomPromise((resole, reject) => {
// 这里的内容在执行器中,会立即执行
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
const x = onFulfilled(this.value)
resolvePromise(x, resole, reject)
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason)
} else if (this.status === PENDING) {
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到promise状态切为成功或失败时再执行
// this.onFulfilledCallback = onFulfilled
// this.onRejectedCallback = onRejected
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
})
return promise2
}
function resolvePromise(x, resolve, reject) {
// 判断x是不是 CustomPromise 实例对象
if (x instanceof CustomPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else {
// 普通值
resolve(x)
}
}
// 1
// resolve ok
// 2
// resolve2 success
resolvePromise里的resolve与reject是为了将then函数新生成的promise切成fulfilled或rejected,要不然执行下一个then时,就会出现由于新生成的promise状态永远为pending,回调函数不会被调用,至于参数值自然是为了传给下一个then函数
五、then 方法链式调用识别 Promise 是否返回自己
在控制台打印如下:
const promise = new Promise((resolve, reject) => {
resolve(100)
})
const p1 = promise.then(value => {
console.log(value)
return p1
})
// undefined
// editor.csdn.net/:1 Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
如果 then 方法返回的是自己的 Promise 对象,则会发生循环调用,这个时候程序会报错
功能完善:
将then函数新生成的promise作为参数传入resolvePromise
// resolvePromise 集中处理,将 新生成的promise 传入
resolvePromise(promise2, x, resolve, reject);
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
if(x instanceof MyPromise) {
x.then(resolve, reject)
} else{
resolve(x)
}
}
验证:
报错
return p1
^
ReferenceError: Cannot access 'p1' before initialization
分析原因:
从错误提示说明,我们要等promise2 完成初始化。这里就需要创建一个异步函数去等待promise2完成初始化。
网上大多采用setTimeout,个人建议用queueMicrotask,因为setTimeout为宏任务,而queueMicrotask跟promise是微任务
改造:
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
// 调用成功回调,并且把值返回
const x = onFulfilled(this.value)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
})
验证:
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
resolve('ok')
})
const p1 = p.then((value) => {
console.info(1)
console.info('resolve', value)
return p1
})
p1.then(
(value) => {
console.info(2)
console.info('resolve', value)
},
(reason) => {
console.info(reason)
}
)
// 1
// resolve ok
// TypeError: Chaining cycle detected for promise #<Promise>
六、捕获错误及 then 链式调用其他状态代码补充
-
捕获执行器错误
捕获执行器中的代码,如果执行器中有代码错误,那么 Promise 的状态要变为失败
功能完善:constructor(executor) { // executor 是一个执行器,进入会立即执行 // 并传入resolve和reject方法 try { executor(this.resolve, this.reject) } catch (error) { // 如果有错误,就直接执行 reject this.reject(error) } }
验证:
const CustomPromise = require('./CustomPromise') const p = new CustomPromise((resolve, reject) => { throw new Error('执行器错误') }) p.then( (value) => { console.info(1) console.info('resolve', value) }, (reason) => { console.info(2) console.info(reason.message) } ) // 2 // 执行器错误
-
then执行时的错误捕获
功能完善:// 创建一个微任务等待 promise2 完成初始化 queueMicrotask(() => { try { // 调用成功回调,并且把值返回 const x = onFulfilled(this.value) // 传入 resolvePromise 集中处理 resolvePromise(promise2, x, resolve, reject) } catch (err) { reject(err) } })
验证:
const CustomPromise = require('./CustomPromise') const p = new CustomPromise((resolve, reject) => { resolve('ok') }) p.then( (value) => { console.info(1) console.info('resolve', value) throw new Error('callback error') }, (reason) => { console.info(2) console.info(reason.message) } ).then( (value) => { console.info(3) console.info(value) }, (err) => { console.info(4) console.info(err.message) } ) // 1 // resolve ok // 4 // callback error
七、参考fulfilled 状态下的处理方式,对 rejected 和 pending 状态进行改造
功能完善:
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
// 为了实现链式调用,创建个新的 CustomPromise 对象,并返回
const promise2 = new CustomPromise((resolve, reject) => {
// 这里的内容在执行器中,会立即执行
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用成功回调,并且把值返回
const x = onFulfilled(this.value)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = onRejected(this.reason)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
}
})
return promise2
}
验证:
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
resolve('ok')
})
p.then(
(value) => {
console.info(1)
console.info('resolve', value)
throw new Error('callback error')
},
(reason) => {
console.info(2)
console.info(reason.message)
}
)
.then(
(value) => {
console.info(3)
console.info(value)
},
(err) => {
console.info(4)
console.info(err.message)
return err.message
}
)
.then((value) => {
console.info(5)
console.info(value)
throw new Error('err') // 进入下一个then里的onRejected
// return new Error('err') // 进入下一个then里的onFulfilled
})
.then(
(value) => {
console.info(6)
console.info(value)
},
(err) => {
console.info(7)
console.info(err.message)
}
)
// 1
// resolve ok
// 4
// callback error
// 5
// callback error
// 7
// err
八、then中的参数变为可选(即实现值传递)
当不传入onFulfilled回调函数时,默认为value=>value
当不传入onRejected回调函数时,默认为reason => {throw reason}
功能完善:
CustomPromise.prototype.then = function (onFulfilled, onRejected) {
// 如果不传,就使用默认函数
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
}
验证:
const CustomPromise = require('./CustomPromise')
const p = new CustomPromise((resolve, reject) => {
resolve('ok')
})
p.then()
.then()
.then((value) => console.log(value))
// ok
const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
reject('err')
})
promise.then().then().then(value => console.log(value), reason => console.log(reason))
// err
九、实现 resolve 与 reject 的静态调用
const CustomPromise = require('./CustomPromise')
CustomPromise.resolve()
.then(() => {
console.log(0)
return CustomPromise.resolve(4)
})
.then((res) => {
console.log(res)
})
// CustomPromise.resolve()
// ^
// TypeError: CustomPromise.resolve is not a function
结果不出意外的报错了,因为根本没有定义这些静态方法
功能完善:
// resolve 静态方法
static resolve(parameter) {
// 如果传入 CustomPromise 就直接返回
if (parameter instanceof CustomPromise) {
return parameter
}
// 转成常规方式
return new CustomPromise((resolve) => {
resolve(parameter)
})
}
// reject 静态方法
static reject(reason) {
return new CustomPromise((resolve, reject) => {
reject(reason)
})
}
验证:
const CustomPromise = require('./CustomPromise')
CustomPromise.resolve()
.then(() => {
console.log(0)
return CustomPromise.resolve(4)
})
.then((res) => {
console.log(res)
})
// 0
// 4
九、补齐 Promise 的API
- catch
Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then。
功能完善:
CustomPromise.prototype.catch = function (errCallback) {
return this.then(null, errCallback)
}
验证:
const CustomPromise = require('./CustomPromise')
CustomPromise.resolve(
new CustomPromise((resolve, reject) => {
setTimeout(() => {
reject('fail')
}, 1000)
})
)
.then((value) => {
console.info(value, 'value')
})
.catch((err) => {
console.info(err, 'err')
})
// fail err
- finally
finally不是最终的意思,而是无论如何都会执行的意思。相当于要在then的成功与失败回调里都要执行finally的回调函数
如果是进入then的成功回调,则要将结果返回;
如果是进入then的失败回调,则要将失败结果传到catch中。
功能完善:
CustomPromise.prototype.finally = function (callback) {
return this.then(
(value) => {
return CustomPromise.resolve(callback()).then(() => value)
},
(reason) => {
return CustomPromise.resolve(callback()).then(() => {
throw reason
})
}
)
}
验证:
CustomPromise.resolve(456)
.finally(() => {
return new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve(123)
}, 3000)
})
})
.then((data) => {
console.log(data, 'success')
})
.catch((err) => {
console.log(err, 'error')
})
// 456 success
- all
promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)
功能完善:
CustomPromise.all = function (values) {
if (!Array.isArray(values)) {
const type = typeof values
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}
return new CustomPromise((resolve, reject) => {
let resultArr = []
let orderIndex = 0
const processResultByKey = (value, index) => {
resultArr[index] = value
if (++orderIndex === values.length) {
resolve(resultArr)
}
}
for (let i = 0; i < values.length; i++) {
let value = values[i]
if (value && typeof value.then === 'function') {
value.then((value) => {
processResultByKey(value, i)
}, reject)
} else {
processResultByKey(value, i)
}
}
})
}
验证:
const CustomPromise = require('./CustomPromise')
let p1 = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok1')
}, 1000)
})
let p2 = new CustomPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok2')
}, 1000)
})
CustomPromise.all([1, 2, 3, p1, p2]).then(
(data) => {
console.log('resolve', data)
},
(err) => {
console.log('reject', err)
}
)
// 1s 后输出
// resolve [ 1, 2, 3, 'ok1', 'ok2' ]
- race
功能完善:
static race(promises) {
return new CustomPromise((resolve, reject) => {
// 一起执行就是for循环
for (let i = 0; i < promises.length; i++) {
let val = promises[i]
if (val && typeof val.then === 'function') {
val.then(resolve, reject)
} else {
// 普通值
resolve(val)
}
}
})
}