想要实现 Promise,必须先了解 Promise 是什么,以及 Promise 有哪些功能。
还不是特别了解 Promise 的同学,建议先移步 ES6入门-Promise 熟悉。
Promise 是基于 Promises/A+ 规范 实现的,换句话说,我们可以按照 Promises/A+ 规范 来手写 Promise。
Promise,直译过来就是承诺,Promise 到底承诺了什么呢?
当我在麦当劳点一份汉堡套餐,收银员会给我一张收据,这个收据就是 Promise,代表我已经付过钱了,麦当劳会为我做一个汉堡套餐的承诺,我要通过收据来取这个汉堡套餐。
那么这个买汉堡得到的承诺会有以下 3 种状态:
需要注意的是,状态的修改是不可逆的,当汉堡做好了,承诺兑现了,就不能再回到等待状态了。
总结一下,Promise 就是一个承诺,承诺会给你一个处理结果,可能是成功的,可能是失败的,而返回结果之前,你可以同时做其他事情。
接下来,按照 Promises/A+ 规范 一步步实现 Promise。
先瞅一眼 ES6 Promise 基本用法。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value)
} else {
reject(error)
}
});
promise.then(data => {
console.log('请求成功')
}, err => {
console.log('请求失败')
})
Promise 拥有自己的状态,初始状态->成功状态时,执行成功回调,初始状态->失败状态时,执行失败回调。
fulfilled
或 rejected
状态;通过已知的 Promise 3 种状态,可定义常量 STATUS 和 MyPromise 状态 status。
代码如下:
// Promise 3 种状态
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
class MyPromise {
// 初始状态为 pending
status = STATUS.PENDING
}
从基本用法可知,Promise 需要接收 1 个执行器函数作为参数,这个函数带有 2 个参数。
代码如下:
class MyPromise {
constructor (executor) {
// 执行器
executor(this.resolve, this.reject)
}
// 成功返回值
value = null
// 失败返回值
reason = null
// 修改 Promise 状态,并定义成功返回值
resolve = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
}
}
// 修改 Promise 状态,并定义失败返回值
reject = reason => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = reason
}
}
}
}
Promise 拥有 then
方法,then 方法第一个参数是成功状态的回调函数 onFulfilled
,第二个参数是失败状态的回调函数 onRejected
。
promise.then(onFulfilled, onRejected)
onFulfilled 要求如下:
onRejected 要求如下:
代码如下:
class MyPromise {
then = function (onFulfilled, onRejected) {
if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
} else if (this.status === STATUS.REJECTED) {
onRejected(this.reason)
}
}
}
按照 Promise 的基本用法,创建 MyPromise 实例 mypromise。
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
mypromise.then(data => {
console.log(data, '请求成功') // 成功打印“成功 请求成功”
}, err => {
console.log(err, '请求失败')
})
下文将按照 Promises/A+ 规范 完善 MyPromise.then 方法。
Promises/A+ 规范 中标明 then 有以下要求:
onFulfilled、onRejected 是可选参数。
代码如下:
class MyPromise {
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
}
}
then 可以在同一个承诺上多次调用。
可以理解为将 onFulfilled、onRejected 作为数组存储在 MyPromise 中,然后按照顺序执行。
代码如下:
class MyPromise {
// 成功回调
onFulfilledCallback = []
// 失败回调
onRejectedCallback = []
// 修改 Promise 状态,并定义成功返回值
resolve = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
while(this.onFulfilledCallback.length) {
this.onFulfilledCallback.shift()(value)
}
}
}
// 修改 Promise 状态,并定义失败返回值
reject = reason => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = reason
while(this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(reason)
}
}
}
then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
if (this.status === STATUS.PENDING) {
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.push(onRejected)
} else if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
} else if (this.status === STATUS.REJECTED) {
onRejected(this.reason)
}
}
}
由此,我们已实现了一个基础的 Promise。
看了这么久,试一试 MyPromise 是否符合要求吧。
代码如下:
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
mypromise.then(data => {
console.log(data, '1')
})
mypromise.then(data => {
console.log(data, '2')
})
输出结果如图:
由图可知,和预期一样。
then 必须返回一个 Promise 来支持链式调用 Promise。
示例代码如下:
mypromise.then(data => {
console.log(data, '请求成功')
return '2'
}).then(data => {
console.log(data, '请求成功')
return '3'
})
改动点如下:
class MyPromise {
then = function (onFulfilled, onRejected) {
// 返回 MyPromise实例
const promise2 = new MyPromise((resolve, reject) => {
if (this.status === STATUS.PENDING) {
this.onFulfilledCallback.push(() => {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
})
this.onRejectedCallback.push(() => {
const x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
})
} else if (this.status === STATUS.FULFILLED) {
const x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} else if (this.status === STATUS.REJECTED) {
const x = onRejected(this.error)
resolvePromise(promise2, x, resolve, reject)
}
})
return promise2
}
}
上述代码引用了 resolvePromise 来处理 Promise.then 的返回值
这里的promise2暂时还不能正常运行,可能会报错:Cannot access 'promise2' before initialization
| 不能访问 promise2 在初始化之前。
原因:在 new promise 时,promise2 还没有完成初始化。所以 resolvePromise 中不能访问到 promise2,在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的,onFulfilled 或 onRejected 得是在当前事件循环后异步执行的。
解决方法:可以使用 setTimeout、setImmediate、MutationObserever、process.nextTick在 then 方法被调用后将创建一个新的栈,这个我们后续处理,先正常往下看。
Promises/A+ 规范 对resolvePromise 的要求如下:
代码如下:
function resolvePromise (promise2, x, resolve, reject) {
// 如果 promise2 === x, 执行 reject,错误原因为 TypeError
if (promise2 === x) {
reject(new TypeError('The promise and the return value are the same'))
}
// 如果 x 是函数或对象
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
let then
try {
then = x.then
} catch (error) {
reject(error)
}
// 如果 x.then 是函数
if (typeof then === 'function') {
// Promise/A+ 2.3.3.3.3 只能调用一次
let called = false
try {
then.call(x, y => {
// resolve的结果依旧是promise 那就继续解析 | 递归解析
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, err => {
if (called) return
called = true
reject(err)
})
} catch (error) {
if (called) return
reject(error)
}
} else {
// 如果 x.then 不是函数
resolve(x)
}
} else {
// 如果 x 不是 promise 实例
resolve(x)
}
}
试试看能不能符合预期,链式调用 then 吧。
输出结果为:
成功符合预期!
Promises/A+ 规范 要求 onFulfilled、onRejected 在执行上下文堆栈之前不得调用。也就是3.1.1标明要注意的点。
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为事件队列。
当所有同步任务执行完成后,系统才会读取”事件队列”。
事件队列中的事件分为宏任务和微任务:
事件队列就是先执行微任务,再执行宏任务,而宏任务和微任务包含以下事件:
宏任务 | 微任务 |
---|---|
setTimeout | Promise |
setInterval | queueMicrotask |
script(整体代码块) | - |
看看下面这个例子,你知道答案吗?
setTimeout(function () {
console.log(1)
})
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val)
})
console.log(4)
打印结果的顺序是2->4->3->1。事件队列如下:
主队列,同步任务,new Promise 内部的同步任务
new Promise(function(resolve,reject){
console.log(2)
})
主队列,同步任务,new Promise 后的 console.log(4)
console.log(4)
异步任务的微任务
promise.then(function(val){
console.log(val)
})
异步任务的宏任务
setTimeout(function () {
console.log(1)
})
因此,想要实现 onFulfilled、onRejected 在执行上下文堆栈之前不得调用,我们需要把 onFulfilled、onRejected 改造成微任务,这里使用 queueMicrotask 来模拟实现微任务,代码如下:
class MyPromise {
then (onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
const realOnRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
if (this.status === STATUS.PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
} else if (this.status === STATUS.FULFILLED) {
fulfilledMicrotask()
} else if (this.status === STATUS.REJECTED) {
rejectedMicrotask()
}
})
return promise2
}
}
打印结果如图:
成功按顺序打印。
下面将用 Promise/A+ 测试工具 promises-aplus-tests 测试我们手写的 Promise 是否符合规范。
npm install promises-aplus-tests -D
MyPromise {
......
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve
result.reject = reject
});
return result;
}
module.exports = MyPromise
"scripts": {
"test:promise": "promises-aplus-tests ./Promise/index"
}
npm run test:promise
哇哦,全部成功!!
截止到此,完整代码如下:
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
class MyPromise {
constructor (executor) {
// 执行器
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 初始状态为 pending
status = STATUS.PENDING
// 成功返回值
value = null
// 失败返回值
reason = null
// 成功回调
onFulfilledCallbacks = []
// 失败回调
onRejectedCallbacks = []
// 修改 Promise 状态,并定义成功返回值
resolve = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
// this.onFulfilledCallbacks.forEach(fn => fn())
while(this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value)
}
}
}
// 修改 Promise 状态,并定义失败返回值
reject = reason => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = reason
// this.onRejectedCallbacks.forEach(fn => fn())
while(this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then = function (onFulfilled, onRejected) {
// onFulfilled、onRejected 是可选参数 不是函数则必须忽略它
const realOnFulfilled = onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
const realOnRejected = onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
const promise2 = new MyPromise((resolve, reject) => {
// 报错:Cannot access 'promise2' before initialization | 不能访问 promise2 在初始化之前
// 原因:在 new promise 时,promise2 还没有完成初始化,所以 resolvePromise 中不能访问到 promise2
// 在当前的执行上下文栈中,onFulfilled 或 onRejected 是不能被直接调用的
// onFulfilled 或 onRejected 得是在当前事件循环后异步执行的
// 可以使用 setTimeout、setImmediate、MutationObserever、process.nextTick在 then 方法被调用后将创建一个新的栈
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnRejected(this.reason)
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject)
} catch (error) {
reject(error)
}
})
}
if (this.status === STATUS.PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onRejectedCallbacks.push(rejectedMicrotask)
} else if (this.status === STATUS.FULFILLED) {
fulfilledMicrotask()
} else if (this.status === STATUS.REJECTED) {
rejectedMicrotask()
}
})
return promise2
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果 promise2 === x 执行 reject,错误原因为 TypeError
if (promise2 === x) {
reject(new TypeError('The promise and the return value are the same'))
}
// 如果 x 是函数或对象
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
let then
try {
then = x.then
} catch (error) {
reject(error)
}
// 如果 x.then 是函数
if (typeof then === 'function') {
// Promise/A+ 2.3.3.3.3 只能调用一次
let called = false
try {
then.call(x, y => {
// resolve的结果依旧是promise 那就继续解析 | 递归解析
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, err => {
if (called) return
called = true
reject(err)
})
} catch (error) {
if (called) return
reject(error)
}
} else {
// 如果 x.then 不是函数
resolve(x)
}
} else {
// 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4
resolve(x)
}
}
// 测试 Promise 是否符合规范
MyPromise.deferred = function () {
var result = {}
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve
result.reject = reject
})
return result
}
module.exports = MyPromise
下面的完善Promise 的 API将基于此基础代码。
虽然上述的 promise 源码已经符合 Promise/A+ 的规范,但是原生的 Promise 还提供了一些其他方法,如:
下面具体说一下每个方法的实现:
Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then。
至于为什么先实现此方法,是为了防止实现其他 api 时会报错。
// ...
catch(errCallback) {
return this.then(null,errCallback)
}
// ...
finally
表示不是最终的意思,而是无论如何都会执行的意思。 如果返回一个 promise
会等待这个 promise
也执行完毕。如果返回的是成功的 promise
,会采用上一次的结果;如果返回的是失败的 promise
,会用这个失败的结果,传到 catch
中。
finally(callback) {
return this.then((value)=>{
return MyPromise.resolve(callback()).then(()=>value)
},(reason)=>{
return MyPromise.resolve(callback()).then(()=>{throw reason})
})
}
测试一下
MyPromise.resolve(456).finally(()=>{
return new MyPromise((resolve,reject)=>{
setTimeout(() => {
resolve(123)
}, 3000)
})
}).then(data=>{
console.log(data,'success')
}).catch(err=>{
console.log(err,'error')
})
控制台等待 3s
后输出:456 'success'
,相反如果把resolve(123)
改为reject(123)
等待 3s
后输出123 'error'
默认产生一个成功的 promise。
static resolve(data){
return new MyPromise((resolve,reject)=>{
resolve(data)
})
}
这里需要注意的是,promise.resolve 是具备等待功能的。如果参数是 promise 会等待这个 promise 解析完毕,在向下执行,所以这里需要在原来 resolve
方法中做一个小小的处理:
// 修改 Promise 状态,并定义成功返回值
resolve = value => {
if(value instanceof MyPromise){
// 递归解析
return value.then(this.resolve,this.reject)
}
if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = value
while(this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value)
}
}
}
static reject(reason){
return new MyPromise((resolve,reject)=>{
reject(reason)
})
}
Promise.all
是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。
Promise.all
方法可以接收一个promise数组作为参数,返回一个新的promise对象,该对象在数组中所有promise都成功时才会被resolve。如果其中有一个promise失败,则Promise.all
会立即将其reject,并且不再等待其他promise的执行结果。
注意:这个参数数组里面也不是必须都是promise,也可以是常量普通值。
static all(values) {
if (!Array.isArray(values)) {
const type = typeof values
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}
return new MyPromise((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)
}
}
})
}
测试一下:
let p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok1')
}, 1000)
})
let p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('ok2')
}, 1000)
})
MyPromise.all([1,2,3,p1,p2]).then(data => {
console.log('resolve', data)
}, err => {
console.log('reject', err)
})
控制台等待 1s
后输出:resolve (5) [1, 2, 3, 'ok1', 'ok2']
Promise.race
用来处理多个请求,采用最快的(谁先完成用谁的)。
static race(promises) {
return new MyPromise((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)
}
}
})
}
特别需要注意的是:因为Promise 是没有中断方法的,xhr.abort()、ajax 有自己的中断方法,axios 是基于 ajax 实现的;fetch 基于 promise,所以他的请求是无法中断的。
这也是 promise 存在的缺陷,我们可以使用 race 来自己封装中断方法:
function wrap(promise) {
// 在这里包装一个 promise,可以控制原来的promise是成功还是失败
let abort
let newPromise = new MyPromise((resolve, reject) => { // defer 方法
abort = reject
});
let p = MyPromise.race([promise, newPromise]) // 任何一个先成功或者失败 就可以获取到结果
p.abort = abort
return p
}
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => { // 模拟的接口调用 ajax 肯定有超时设置
resolve('成功')
}, 1000);
});
let newPromise = wrap(promise)
setTimeout(() => {
// 超过3秒 就算超时 应该让 proimise 走到失败态
newPromise.abort('超时了')
}, 3000)
newPromise.then((data => {
console.log('成功的结果' + data)
})).catch(e => {
console.log('失败的结果' + e)
})
控制台等待 1s
后输出:成功的结果成功
以上,我们实现了一个符合 Promises/A+ 规范 的 Promise,也实现了Promise
一些常用API方法。
总结一下 Promise
其实就是一个帮助我们执行异步任务的对象,因为 Javascript 单线程的特性,导致必须通过为异步任务添加回调来得到异步任务的结果。为了解决回调地狱,Promise 应运而生。
Promise
通过对异步任务执行状态的处理,让我们可以在 Promise.then
中获取任务结果,让代码更加清晰优雅。
Promise.then
的链式调用,以顺序的方式来表达异步流,让我们更好的维护异步代码。
注:该文基于 https://juejin.cn/post/6973155726302642206 基础上转载修改完善,感谢。