··18 阅读·预计 21 分钟
JavaScript Promise 完全指南:从原理到手写实现
Promise 是 JavaScript 异步编程的核心。本文从规范出发,深入理解 Promise 的运行机制,并手写一个符合 Promise/A+ 规范的实现。
一、为什么需要 Promise
回调地狱的问题
// 回调地狱:代码难以阅读和维护
getUser(userId, function(user) {
getOrders(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
// 嵌套越来越深...
});
});
});
Promise 解决了什么
- 链式调用 - 扁平化异步流程
- 错误处理 - 统一的 catch 机制
- 状态管理 - 明确的 pending/fulfilled/rejected 三种状态
二、Promise 核心概念
三种状态
const promise = new Promise((resolve, reject) => {
// pending: 初始状态
// fulfilled: 操作成功
resolve(value);
// 或 rejected: 操作失败
reject(reason);
});
关键特性:
- 状态只能改变一次(pending → fulfilled 或 pending → rejected)
- 改变后不可逆,称为 resolved(已定型)
then 方法的核心规则
promise.then(onFulfilled, onRejected);
onFulfilled和onRejected是可选参数- 如果不是函数,必须被忽略
onFulfilled在 fulfilled 状态调用,参数是 valueonRejected在 rejected 状态调用,参数是 reasonthen必须返回一个新的 Promise
值穿透
Promise.resolve(1)
.then() // 值穿透:跳过非函数参数
.then() // 继续穿透
.then(val => {
console.log(val); // 1
});
三、手写 Promise 实现
基础结构
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
// 存储回调(因为可能异步 resolve)
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
}
then 方法实现
then(onFulfilled, onRejected) {
// 值穿透:参数不是函数时,创建默认处理函数
onFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value;
onRejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason };
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledTask = () => {
// 异步执行(微任务)
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const rejectedTask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this.status === FULFILLED) {
fulfilledTask();
} else if (this.status === REJECTED) {
rejectedTask();
} else {
// pending 状态,存储回调
this.onFulfilledCallbacks.push(fulfilledTask);
this.onRejectedCallbacks.push(rejectedTask);
}
});
return promise2;
}
resolvePromise 处理返回值
这是 Promise/A+ 规范最复杂的部分:
resolvePromise(promise2, x, resolve, reject) {
// 循环引用检测
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
// x 是 Promise
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
// x 是对象或函数
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false; // 防止多次调用
try {
const then = x.then;
// then 是函数(thenable)
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
// 递归处理(y 可能还是 promise)
this.resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 普通对象
resolve(x);
}
} catch (e) {
if (called) return;
reject(e);
}
} else {
// 基本类型
resolve(x);
}
}
catch 和静态方法
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((p, i) => {
MyPromise.resolve(p).then(
val => {
results[i] = val;
if (++count === promises.length) {
resolve(results);
}
},
reject
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(p => {
MyPromise.resolve(p).then(resolve, reject);
});
});
}
static allSettled(promises) {
return new MyPromise(resolve => {
const results = [];
let count = 0;
promises.forEach((p, i) => {
MyPromise.resolve(p).then(
value => {
results[i] = { status: 'fulfilled', value };
if (++count === promises.length) resolve(results);
},
reason => {
results[i] = { status: 'rejected', reason };
if (++count === promises.length) resolve(results);
}
);
});
});
}
四、事件循环与微任务
Promise 的回调在微任务队列执行:
console.log(1);
Promise.resolve().then(() => {
console.log(2);
});
console.log(3);
// 输出顺序:1, 3, 2
事件循环流程:
- 执行同步代码
- 执行微任务队列(Promise.then、queueMicrotask)
- 执行宏任务队列(setTimeout、setInterval)
- 更新渲染
- 重复
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));
console.log('script end');
// 输出:
// script start
// script end
// promise1
// promise2
// setTimeout
五、常见陷阱与最佳实践
陷阱 1:忘记 return
// 错误:没有 return,后面的 then 拿不到结果
Promise.resolve(1)
.then(val => {
Promise.resolve(val + 1);
})
.then(val => {
console.log(val); // undefined
});
// 正确
Promise.resolve(1)
.then(val => {
return Promise.resolve(val + 1);
})
.then(val => {
console.log(val); // 2
});
陷阱 2:Promise 构造函数反模式
// 错误:不必要地包装 Promise
function getData() {
return new Promise((resolve, reject) => {
fetch('/api/data')
.then(res => resolve(res.json()))
.catch(err => reject(err));
});
}
// 正确:直接返回
function getData() {
return fetch('/api/data').then(res => res.json());
}
最佳实践:async/await
// Promise 链式调用
function fetchUser() {
return getUser()
.then(user => getOrders(user.id))
.then(orders => getOrderDetail(orders[0].id))
.catch(err => console.error(err));
}
// async/await 更清晰
async function fetchUser() {
try {
const user = await getUser();
const orders = await getOrders(user.id);
const detail = await getOrderDetail(orders[0].id);
return detail;
} catch (err) {
console.error(err);
}
}
并发控制
// Promise.all:全部成功才成功
const results = await Promise.all([
fetch('/api/1'),
fetch('/api/2'),
fetch('/api/3'),
]);
// Promise.allSettled:不管成功失败
const results = await Promise.allSettled([
fetch('/api/1'),
fetch('/api/2'),
]);
// 限制并发数
async function limitConcurrency(tasks, max) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const p = Promise.resolve().then(() => task());
results.push(p);
executing.add(p);
const cleanup = () => executing.delete(p);
p.then(cleanup, cleanup);
if (executing.size >= max) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
六、总结
- 核心:Promise 是状态机,状态只能改变一次
- then:返回新 Promise,支持链式调用和值穿透
- resolvePromise:处理返回值,支持 thenable 和循环引用检测
- 微任务:回调在微任务队列异步执行
- 实践:优先使用 async/await,注意 return 和错误处理
理解 Promise 的原理,才能写出更健壮的异步代码。
3 评论
评论区
登录 后参与评论
c
climberzbm大约 2 个月前
写的不错
管
管理员大约 2 个月前
感谢认可!Promise 是前端面试高频考点,希望这篇文章能帮你彻底搞懂它 💪 有问题随时交流~
管
管理员大约 1 个月前
感谢认可!🙌 Promise确实是JavaScript中比较难理解的概念,能帮到你我也很开心~ 有什么问题欢迎随时交流!