个人博客:haichenyi.com。感谢关注
一. 目录
二. Promise 基础概念回顾
上一篇掌握promise中就已经讲过了
- 什么是 Promise?
- promise是JavaScript中在ES6中新增的异步操作的解决方案
- 本质是一个状态机,包含三种状态:pending(等待),fulfilled(成功),rejected(失败)
- 通过.then()方法链式调用,解决传统回调地狱的问题
- 核心特新
- 状态不可逆: 一旦状态变为fulfilled或者rejected之后,就不可改变了
- 链式调用: 每个.then()返回一个新的promise
- 错误冒泡: 链式调用中,错误会一直向后传递,直到被.catch()捕获
三. 深入原理:Promise 如何工作?
- 事件循环与微任务(Microtask)
- 之前异步编程中就讲过了,promise的回调属于微任务,优先级高于宏任务(setTimeout,绑定事件等等)
- 当promise状态发生改变,.then() 的回调会被放进微任务队列,等待当前宏任务执行完毕之后立即执行
console.log(1);
Promise.resolve().then(() => console.log(2)); // 微任务
setTimeout(() => console.log(3)); // 宏任务
console.log(4);
// 输出顺序:1 → 4 → 2 → 3
- 链式调用的实现
- 每个 .then() 方法都返回一个新的promise,形成链式结构
- 如果前一个 .then()的回调返回一个值(或新的promise),该值会成为下一个 then()的输入
new Promise((resolve) => resolve(1))
.then((val) => val + 1) // 返回 2
.then((val) => Promise.resolve(val + 1)) // 返回 Promise(3)
.then(console.log); // 输出 3
3. 错误处理机制
- 如果链式调用中某一步出现错误(reject 或 throw),会跳过后续所有 .then(),直接进入最近的 .catch()。
- 内部通过 try/catch 捕获回调中的错误,并传递给下一个 Promise。
四. 手写实现 Promise(简化版)
我们将手把手教你实现一个符合Promise/A+版本的promise,核心逻辑如下:
要看注释,注释才是为什么这么写的思路
要看注释,注释才是为什么这么写的思路
要看注释,注释才是为什么这么写的思路
export default class MyPromise {
// new Promise((resolve,reject)=>{
// })
//1.构造方法需要传递一个executor,包含两个参数(resolve,reject)
//这两个参数都是方法,并且,方法接收一个参数,resolve(value),reject(reason)
//要注意,这两个方法是promise内部定义的,并不是外部传递进来的。
constructor(executor) {
}
//2.有三个状态Pending(等待),Fulfilled(成功),Rejected(失败)。
//并且状态改变不可逆,resolve方法,把状态从pending——>fulfilled
//reject方法,把状态从pending——>Rejected
//需要一个成员变量记录当前状态
static PENDING = "Pending"
static FULFILLED = "Fulfilled"
static REJECTED = "Rejected"
state = MyPromise.PENDING
}
经过上面这两步,我们的类就变成了:
export default class MyPromise {
//2.有三个状态Pending(等待),Fulfilled(成功),Rejected(失败)。
//并且状态改变不可逆,resolve方法,把状态从pending——>fulfilled
//reject方法,把状态从pending——>Rejected
//需要一个成员变量记录当前状态
static PENDING = "Pending"
static FULFILLED = "Fulfilled"
static REJECTED = "Rejected"
state = MyPromise.PENDING
//经过上面这两步,构造方法就变成了:
constructor(executor) {
//定义初始化状态
this.state = MyPromise.PENDING
//定义成功回调的参数
this.value = undefined
//定义失败回调的参数
this.reason = undefined
//定义成功回调方法
const resolve = (value) => {
if (this.state !== MyPromise.PENDING) {
//不是初始值,就终止
return
}
//状态改为成功
this.state = MyPromise.FULFILLED
//成功赋值
this.value = value
}
//定义失败回调方法
const reject = (reason) => {
if (this.state !== MyPromise.PENDING) {
//不是初始值,就终止
return
}
//状态改为失败
this.state = MyPromise.REJECTED
//失败赋值
this.reason = reason
}
try {
//执行异步方法。这才是关键点
//前面new的时候只是把方法传递进来了,并没有执行。
//也就是说,你在使用的时候,把你的网络请求方法传递进来了,如果没有这一步,你的网络请求永远不会执行
//执行之后,把成功回调,和失败回调暴露给你,你根据实际情况调用。
//Promise的状态都是封装好的,你也不用关心
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
}
这个时候,就会有大聪明了,这么简单,试一下
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(aaa)
}, 1000);
})
写到这里,你就会发现,我resolve了之后,没人去接收aaa这个值啊,官方的promise都是用then方法去接收的。
没错,接下来,我们来实现这个then方法。我们上一篇说过,then方法有两个参数,一个是onFulfilled(成功回调),一个是onRejected(失败回调)
then(onFulfilled, onRejected) {
//1.处理简单的边界情况,非函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
//2.then方法的返回值还是一个promise,所以这里就要定义一个promise
const promise2 = new MyPromise((resolve, reject) => {
})
return promise2
}
接下来就是重点了,到底要怎么触发这个逻辑呢?
//我们都知道,一个回调对应一个状态,成功回调对应成功状态。所以
const promise2 = new MyPromise((resolve, reject) => {
//成功执行的函数
const handleFulfilled = () => {
}
//失败执行的函数
const handleRejected = () => {
}
if (this.state === MyPromise.FULFILLED) {
handleFulfilled()
} else if (this.state === MyPromise.REJECTED) {
handleRejected()
} else {
}
})
所以,代码就变成上面这个样子了。我差一个题外话
如上面这个图,同一个promise,是可以调用多个then方法的。每一个then方法都有成功失败的回调。所以,这里需要一个数组去接收。数组放在哪呢?放在构造方法里面初始化。构造方法就变成了如下这个样子了
...//省略其他代码
state = MyPromise.PENDING
//成功回调数组
onFulfilledCallbacks = []
//失败回调数组
onRejectedcallbacks = []
constructor(executor) {
//定义成功回调方法
const resolve = (value) => {
...
//成功赋值
this.value = value
//执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn())
}
//定义失败回调方法
const reject = (reason) => {
...
//失败赋值
this.reason = reason
//执行所有失败回调
this.onRejectedcallbacks.forEach(fn => fn())
}
...//省略其他代码
}
然后,我们的then方法就变成了如下这个样子
then(onFulfilled, onRejected) {
//1.处理简单的边界情况,非函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
//2.then方法的返回值还是一个promise,所以这里就要定义一个promise
const promise2 = new MyPromise((resolve, reject) => {
//成功执行的函数
const handleFulfilled = () => {
}
//失败执行的函数
const handleRejected = () => {
}
//根据当前状态执行
if (this.state === MyPromise.FULFILLED) {
handleFulfilled()
} else if (this.state === MyPromise.REJECTED) {
handleRejected()
} else {
// pending 状态,存入队列
this.onFulfilledCallbacks.push(handleFulfilled)
this.onRejectedcallbacks.push(handleRejected)
}
})
return promise2
}
到这里,我们也只是打好了基础,还是没有写实际执行的代码。也就是我们成功执行的函数和失败执行的函数,那么到底要怎么实现呢?
//成功执行的函数
const handleFulfilled = () => {
// 模拟微任务(此处用 setTimeout 简化,实际浏览器用 queueMicrotask)
setTimeout(() => {
try {
//执行当前then方法的成功回调
const x = onFulfilled(this.value)
} catch (error) {
//执行出错,执行新的promise的reject回调
reject(error)
}
}, 0);
}
//失败执行的函数
const handleRejected = () => {
setTimeout(() => {
try {
//执行当前then方法的失败回调
const x = onRejected(this.reason)
} catch (error) {
//执行出错,执行当前promise的reject回调
reject(error)
}
}, 0);
}
如上代码,处理了当执行then方法回调出错的情况。接下来处理正常的情况
const handleFulfilled = () => {
...
//执行当前then方法的成功回调
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
...
}
//失败执行的函数
const handleRejected = () => {
...
//执行当前then方法的失败回调
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
...
}
//辅助函数:处理返回值可能是 Promise 的情况
resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
到这里,主要逻辑都写完了。完整的代码如下:
class MyPromise {
// new Promise((resolve,reject)=>{
// })
//1.构造方法需要传递一个executor,包含两个参数(resolve,reject)
//这两个参数都是方法,并且,方法接收一个参数,resolve(value),reject(reason)
//要注意,这两个方法是promise内部定义的,并不是外部传递进来的。
// constructor(executor) {
// }
//2.有三个状态Pending(等待),Fulfilled(成功),Rejected(失败)。
//并且状态改变不可逆,resolve方法,把状态从pending——>fulfilled
//reject方法,把状态从pending——>Rejected
//需要一个成员变量记录当前状态
static PENDING = "Pending"
static FULFILLED = "Fulfilled"
static REJECTED = "Rejected"
state = MyPromise.PENDING
//成功回调数组
onFulfilledCallbacks = []
//失败回调数组
onRejectedcallbacks = []
//经过上面这两步,构造方法就变成了:
constructor(executor) {
//定义初始化状态
this.state = MyPromise.PENDING
//定义成功回调的参数
this.value = undefined
//定义失败回调的参数
this.reason = undefined
//定义成功回调方法
const resolve = (value) => {
if (this.state !== MyPromise.PENDING) {
//不是初始值,就终止
return
}
//状态改为成功
this.state = MyPromise.FULFILLED
//成功赋值
this.value = value
//执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn())
}
//定义失败回调方法
const reject = (reason) => {
if (this.state !== MyPromise.PENDING) {
//不是初始值,就终止
return
}
//状态改为失败
this.state = MyPromise.REJECTED
//失败赋值
this.reason = reason
//执行所有失败回调
this.onRejectedcallbacks.forEach(fn => fn())
}
try {
//执行异步方法。这才是关键点
//前面new的时候只是把方法传递进来了,并没有执行。
//也就是说,你在使用的时候,把你的网络请求方法传递进来了,如果没有这一步,你的网络请求永远不会执行
//执行之后,把成功回调,和失败回调暴露给你,你根据实际情况调用。
//Promise的状态都是封装好的,你也不用关心
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
//1.处理简单的边界情况,非函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
//2.then方法的返回值还是一个promise,所以这里就要定义一个promise
const promise2 = new MyPromise((resolve, reject) => {
//成功执行的函数
const handleFulfilled = () => {
// 模拟微任务(此处用 setTimeout 简化,实际浏览器用 queueMicrotask)
setTimeout(() => {
try {
//执行当前then方法的成功回调
const x = onFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
//执行出错,执行新的promise的reject回调
reject(error)
}
}, 0);
}
//失败执行的函数
const handleRejected = () => {
setTimeout(() => {
try {
//执行当前then方法的失败回调
const x = onRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
} catch (error) {
//执行出错,执行当前promise的reject回调
reject(error)
}
}, 0);
}
//根据当前状态执行
if (this.state === MyPromise.FULFILLED) {
handleFulfilled()
} else if (this.state === MyPromise.REJECTED) {
handleRejected()
} else {
// pending 状态,存入队列
this.onFulfilledCallbacks.push(handleFulfilled)
this.onRejectedcallbacks.push(handleRejected)
}
})
return promise2
}
//辅助函数:处理返回值可能是 Promise 的情况
resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
}
测试如下:
关键实现解析:
(1)状态管理
- 通过 state 变量记录当前状态,确保不可逆。
- resolve 和 reject 只能修改 pending 状态。
(2)异步回调队列
- 当 Promise 处于 pending 状态时,.then() 的回调被存入队列。
- 当状态变化时,依次执行队列中的回调。
(3)链式调用
- 每个 .then() 返回新的 promise2。
- 使用 resolvePromise 处理回调返回值:
- 如果返回普通值,直接 resolve(value)。
- 如果返回 Promise,等待其解决。
(4)错误处理
- 使用 try/catch 包裹回调执行,捕获同步错误。
- 异步错误通过返回的 Promise 传递。
五. 与官方 Promise 的区别
- 微任务队列: 官方 Promise 使用微任务(如 queueMicrotask 或 MutationObserver),而此处用 setTimeout 模拟(实际是宏任务)。
- 完整功能: 官方实现包含 .catch()、.finally()、静态方法(如 Promise.all)等。
- 边界情况: 官方处理了更多边缘情况(如循环引用、多次 resolve 等)。
六. 总结
- Promise 的核心是 状态管理 + 回调队列 + 链式调用。
- 手写实现需注意:
- 状态不可变
- 异步回调的执行顺序
- 链式调用中返回新 Promise
- 错误冒泡机制
通过实现一个简化版 Promise,你能更深刻理解 JavaScript 异步编程的设计思想。实际开发中,请直接使用原生 Promise 。