这是我参与更文挑战的第5天,活动详情查看: 更文挑战
一、前言
想要实现 Promise,必须先了解 Promise 是什么,以及 Promise 有哪些功能。
还不是特别了解 Promise 的同学,建议先移步 ES6入门-Promise 熟悉。
Promise 是基于 Promises/A+ 规范 实现的,换句话说,我们可以按照 Promises/A+ 规范 来手写 Promise。
1.1 小例子
Promise,直译过来就是承诺,Promise 到底承诺了什么呢?
当我在麦当劳点一份汉堡套餐,收银员会给我一张收据,这个收据就是 Promise,代表我已经付过钱了,麦当劳会为我做一个汉堡套餐的承诺,我要通过收据来取这个汉堡套餐。
那么这个买汉堡得到的承诺会有以下 3 种状态:
- 等待状态:我刚下单,汉堡还没做好,这时我可以在等待汉堡时,同时做其他事情;
- 成功状态:汉堡做好了,通知我取餐;
- 失败状态:发现卖完了,通知我退款;
需要注意的是,状态的修改是不可逆的,当汉堡做好了,承诺兑现了,就不能再回到等待状态了。
总结一下,Promise 就是一个承诺,承诺会给你一个处理结果,可能是成功的,可能是失败的,而返回结果之前,你可以同时做其他事情。
二、Promises/A+
接下来,按照 Promises/A+ 规范 一步步实现 Promise。
1. 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('请求失败')
})
1.1 Promise 状态
Promise 拥有自己的状态,初始状态->成功状态时,执行成功回调,初始状态->失败状态时,执行失败回调。
- pending:初始状态,可以转换为 fulfilled 或 rejected 状态;
- fulfilled:成功状态,转换到该状态时必须有成功返回值,且不能再次转换状态;
- rejected:失败状态,转换到该状态时必须有错误原因,且不能再次转换状态;
通过已知的 Promise 3 种状态,可定义常量 STATUS 和 MyPromise 状态 status。
代码如下:
// Promise 3 种状态
const STATUS = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected'
}
class MyPromise {
// 初始状态为 pending
status = STATUS.PENDING
}
1.2 执行器
从基本用法可知,Promise 需要接收 1 个执行器函数作为参数,这个函数带有 2 个参数。
- resolve:把 Promise 状态改成成功;
- reject:把 Promise 状态改成失败;
代码如下:
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 = () => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = value
}
}
}
}
1.3 then
Promise 拥有 then 方法,then 方法第一个参数是成功状态的回调函数 onFulfilled,第二个参数是失败状态的回调函数 onRejected。
promise.then(onFulfilled, onRejected)
onFulfilled 要求如下:
- 必须在 promise 状态为完成时调用它,并将 promise 的 value 作为它的第一个参数;
- 在 promise 完成之前不能调用它;
- 它不能被多次调用;
onRejected 要求如下:
- 必须在 promise 被拒绝后调用它,以 promise.reason 作为它的第一个参数;
- 在 promise 被拒绝之前不能调用它;
- 它不能被多次调用;
代码如下:
class MyPromise {
then = function (onFulfilled, onRejected) {
if (this.status === STATUS.FULFILLED) {
onFulfilled(this.value)
} else if (this.status === STATUS.REJECTED) {
onRejected(this.reason)
}
}
}
1.4 试试看
按照 Promise 的基本用法,创建 MyPromise 实例 mypromise。
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
mypromise.then(data => {
console.log(data, '请求成功') // 成功打印“成功 请求成功”
}, err => {
console.log(err, '请求失败')
})
试行成功,打印结果为“成功 请求成功”。
源码地址:基本用法源码
2. Promise.then
下文将按照 Promises/A+ 规范 完善 MyPromise.then 方法。
Promises/A+ 规范 中标明 then 有以下要求:
1. 可选参数
onFulfilled、onRejected 是可选参数。
- 如果 onFulfilled 不是函数,则必须忽略它;
- 如果 onRejected 不是函数,则必须忽略它;
代码如下:
class MyPromise {
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }
}
}
2. 多次调用 then
then 可以在同一个承诺上多次调用。
- 当 promise 完成,所有相应的 onFulfilled 回调必须按照它们的原始调用的顺序执行 then;
- 当 promise 被拒绝,所有相应的 onRejected 回调必须按照它们对 的原始调用的顺序执行 then;
2.1 数组缓存回调
可以理解为将 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 = value => {
if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = value
while(this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(value)
}
}
}
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。
2.2 试试看
看了这么久,试一试 MyPromise 是否符合要求吧。
代码如下:
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
mypromise.then(data => {
console.log(data, '1')
})
mypromise.then(data => {
console.log(data, '2')
})
输出结果如图:
由图可知,和预期一样。
源码地址:多次调用then 源码
3. 链式调用 then
then 必须返回一个 Promise 来支持链式调用 Promise。
示例代码如下:
mypromise.then(data => {
console.log(data, '请求成功')
return '2'
}).then(data => {
console.log(data, '请求成功')
return '3'
})
3.1 改写 then 方法
改动点如下:
- then 方法需要返回 MyPromise 实例;
- then 内部调用回调时,需通过 resolvePromise 方法判断返回值 x 的类型来处理返回值。
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.value)
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 的返回值,
3.2 resolvePromise
Promises/A+ 规范 对resolvePromise 的要求如下:
- 如果 promise2 === x, 执行 reject,错误原因为 TypeError
- 如果 x 是函数或对象
- 如果 x.then 是函数
- 执行 x.then
- 如果 x.then 不是函数
- 执行 resolve(x)
- 如果 x.then 是函数
- 如果 x 不是函数或对象
- 执行 resolve(x)
代码如下:
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' || typeof x === 'function') {
let then
try {
then = x.then
} catch (error) {
reject(error)
}
// 如果 x.then 是函数
if (typeof then === 'function') {
then.call(x, y => {
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
reject(err);// 失败了
})
} else {
// 如果 x.then 不是函数
resolve(x)
}
} else {
// 如果 x 不是 promise 实例
resolve(x)
}
}
3.3 试一试
试试看能不能符合预期,链式调用 then 吧。
const mypromise = new MyPromise((resolve, reject) => {
resolve('成功')
})
const mypromise2 = new MyPromise((resolve, reject) => {
resolve('成功2')
})
mypromise.then(data => {
console.log(data, '1')
return mypromise2
}).then(data => {
console.log(data, '2')
})
输出结果为:
成功符合预期!
源码地址:链式调用then源码
4. 异步事件
Promises/A+ 规范 要求 onFulfilled、onRejected 在执行上下文堆栈之前不得调用。
4.1 事件队列
当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为事件队列。
当所有同步任务执行完成后,系统才会读取"事件队列"。
事件队列中的事件分为宏任务和微任务:
- 宏任务:浏览器/Node发起的任务,如 window.setTimeout;
- 微任务:Js 自身发起的,如 Promise;
事件队列就是先执行微任务,再执行宏任务,而宏任务和微任务包含以下事件:
宏任务 | 微任务 |
---|---|
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
}
}
下面试试能不能成功?
4.2 试试看
const mypromise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('成功'), 1000)
})
const mypromise2 = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('成功2'), 1000)
})
mypromise.then(data => {
console.log(data, '1')
return mypromise2
}).then(data => {
console.log(data, '2')
})
打印结果如图:
成功按顺序打印。
源码地址:异步事件源码
3. Promise/A+ 测试
下面将用 Promise/A+ 测试工具 promises-aplus-tests 测试我们手写的 Promise 是否符合规范。
3.1 安装 promises-aplus-tests
npm install promises-aplus-tests -D
3.2 为 MyPromise 添加 deferred
MyPromise {
......
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = MyPromise;
3.3 配置启动命令
"scripts": {
"test:promise": "promises-aplus-tests ./src/手写系列/Promise/testPromise"
},
3.4 开始测试
npm run test:promise
哇哦,全部成功!!
源码地址:testPromise.js 源码
三、总结
以上,我们实现了一个符合 Promises/A+ 规范 的 Promise,我们可以继续自己动手,参考 ES6 的 Promise 方法对 MyPromise 进行拓展练习。
总结一下 Promise 其实就是一个帮助我们执行异步任务的对象,因为 Javascript 单线程的特性,导致必须通过为异步任务添加回调来得到异步任务的结果。为了解决回调地狱,Promise 应运而生。
Promise 通过对异步任务执行状态的处理,让我们可以在 Promise.then 中获取任务结果,让代码更加清晰优雅。
Promise.then 的链式调用,以顺序的方式来表达异步流,让我们更好的维护异步代码。
可通过 github源码 进行实操练习。
希望能对你有所帮助,感谢阅读~别忘了点个赞鼓励一下我哦,笔芯❤️