Promise 全方位深度讲解
Promise 是 JavaScript 异步编程的核心机制。下面我会从概念、用法、API、原理实现、手写源码、应用场景、常见坑七方面,给你讲透。
一、概念与产生背景
JavaScript 是单线程的,处理异步任务(如网络请求、定时器)的传统方式是回调函数。但嵌套回调会导致"回调地狱":
javascript
getUser(userId, (user) => {
getPosts(user.id, (posts) => {
getComments(posts[0].id, (comments) => {
// ...
});
});
});
Promise 就是这个问题的解决方案。它代表一个将来会完成(或失败)的异步操作的结果。它是一个有状态的对象,可链式调用,并统一错误处理。
二、核心概念:状态与不可变性
一个 Promise 实例有三种状态:
| 状态 | 含义 | 可转变? |
|---|---|---|
| pending (待定) | 初始状态,既不是成功也不是失败 | --- |
| fulfilled (已兑现) | 操作成功完成 | 不可逆 |
| rejected (已拒绝) | 操作失败 | 不可逆 |
状态流转:pending → fulfilled(调用 resolve)
pending → rejected(调用 reject)
一旦状态改变就凝固,不能再变。
javascript
const p = new Promise((resolve, reject) => {
resolve('成功');
reject('失败'); // 无效,状态已是 fulfilled
resolve('二次'); // 同样无效
});
p.then(console.log); // 只会输出 "成功"
三、构造函数与参数
javascript
new Promise(executor)
executor是带有resolve和reject两个参数的函数。executor立即同步执行,Promise 构造函数内部会执行它。resolve(value):将 Promise 状态改为 fulfilled,value 作为结果。reject(reason):将 Promise 状态改为 rejected,reason 作为原因。- 如果
executor执行过程抛出异常,Promise 会自动reject这个错误。
javascript
const p = new Promise((resolve, reject) => {
console.log('1: executor 立即执行');
setTimeout(() => {
resolve('done');
}, 1000);
});
console.log('2: 主线程继续');
// 输出顺序:1 -> 2 -> (1秒后) .then 回调
四、实例方法详解
1. Promise.prototype.then(onFulfilled, onRejected)
- 返回一个新的 Promise,实现链式调用。
onFulfilled:成功后调用的函数,参数是决议值。onRejected:失败后调用的函数,参数是拒绝原因。- 如果
onFulfilled/onRejected返回一个值x,则then返回的新 Promise 会resolve(x)。 - 如果返回一个 Promise,则会等待它 settle,其状态和值将传递给下一个 then。
- 如果抛出异常,新 Promise 变为 rejected。
javascript
Promise.resolve(1)
.then(val => val * 2) // 返回 2,下一个 then 获得值 2
.then(val => Promise.resolve(val + 3)) // 返回 Promise,会展开
.then(console.log); // 输出 5
2. Promise.prototype.catch(onRejected)
等价于 .then(null, onRejected)。专门捕获前面链中抛出的错误或 reject 的原因。
javascript
Promise.reject('出错了')
.catch(err => {
console.error(err);
return '已处理';
})
.then(console.log); // 输出 "已处理"
3. Promise.prototype.finally(onFinally)
- 不论 Promise 是 fulfilled 还是 rejected,最终都会执行
onFinally。 - 回调不接收任何参数。
- 返回一个新的 Promise,其状态和值继承自原 Promise ,除非
onFinally中抛出异常或返回一个 rejected 的 Promise。
javascript
fetch(url)
.then(res => res.json())
.catch(err => console.error(err))
.finally(() => {
console.log('请求结束(无论成功或失败)');
});
注意:finally 的回调返回非 Promise 值不会影响后面的值传递。
五、静态方法详细
1. Promise.resolve(value)
- 如果
value是一个 Promise,直接返回它。 - 如果
value是 thenable 对象(有then方法),会尝试展开(可能异步)。 - 其它情况,返回一个 fulfilled 状态的 Promise。
javascript
Promise.resolve(42).then(console.log); // 42
const thenable = {
then(resolve) { resolve('thenable'); }
};
Promise.resolve(thenable).then(console.log); // 'thenable'
2. Promise.reject(reason)
返回一个 rejected 状态的 Promise,原因就是 reason(不会展开 thenable)。
javascript
Promise.reject(new Error('失败')).catch(err => console.error(err.message));
3. Promise.all(iterable)
- 接收一个可迭代对象(通常是数组),里面是一组 Promise。
- 当所有 Promise 都 fulfilled,返回的 Promise 才会 fulfilled,结果是一个按原顺序排列的结果数组。
- 只要有一个 Promise 被 rejected,立即整体 rejected,原因就是那个失败的原因(快速失败)。
javascript
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(resolve, 100, 2));
const p3 = Promise.reject('error');
Promise.all([p1, p2]).then(console.log); // [1, 2] (100ms 后)
Promise.all([p1, p2, p3]).catch(console.log); // 输出 'error',不会等 p2
4. Promise.allSettled(iterable)
- 所有 Promise 都敲定(无论 fulfilled 还是 rejected)后才返回。
- 结果是一个对象数组,每项都有
status和value/reason。
javascript
Promise.allSettled([p1, p2, p3]).then(results => {
// [
// { status: 'fulfilled', value: 1 },
// { status: 'fulfilled', value: 2 },
// { status: 'rejected', reason: 'error' }
// ]
});
应用场景:当你需要并发多个任务,哪怕某些失败也要获取其他结果时(如批量请求数据后整合展示)。
5. Promise.race(iterable)
- 返回的 Promise 会跟随第一个敲定的 Promise 的状态和值(无论成功或失败)。
- 竞赛:多个 Promise 抢跑,最先到的决定结果。
javascript
const fast = new Promise(resolve => setTimeout(resolve, 50, '快'));
const slow = new Promise(resolve => setTimeout(resolve, 100, '慢'));
Promise.race([fast, slow]).then(console.log); // '快',50ms
常用于设置超时:
javascript
function fetchWithTimeout(url, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), ms)
);
return Promise.race([fetch(url), timeout]);
}
6. Promise.any(iterable)
- 和
all相反,只要有一个 fulfilled,结果就是那个值;如果全部 rejected,返回的 Promise 会 rejected,并带有一个AggregateError。 - 类似于竞赛,但寻找第一个成功的。
javascript
Promise.any([Promise.reject('err1'), Promise.resolve('成功')])
.then(console.log); // '成功'
Promise.any([Promise.reject('a'), Promise.reject('b')])
.catch(e => {
console.log(e.errors); // ['a', 'b']
});
六、实现原理与手写Promise
要真正理解 Promise,需要自己实现一个简易且符合 Promise/A+ 规范的版本。
核心原理:
- Promise 是一个状态机(pending、fulfilled、rejected)。
then方法会注册回调,如果状态还是 pending,就存入队列;如果已经是终态,异步执行(微任务)回调。then返回一个新的 Promise,供链式调用。这个新 Promise 的状态取决于回调函数的返回值。resolve过程如果接收到 Promise 或 thenable,需要递归展开。- 所有的回调都需要异步调用 (通常用
queueMicrotask或setTimeout模拟微任务)。
简易实现:
javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
const resolve = (value) => {
if (this.state !== 'pending') return;
// 如果 resolve 的是一个 Promise,需要递归展开
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
};
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// 值穿透:如果没传 onFulfilled,默认把值往后传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 如果没传 onRejected,默认抛出错误,让后面的 catch 捕获
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
// 统一处理回调,根据返回值 x 决定 promise2 的状态
const fulfillMicrotask = () => {
// 用微任务保证异步
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const rejectMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this.state === 'fulfilled') {
fulfillMicrotask();
} else if (this.state === 'rejected') {
rejectMicrotask();
} else { // pending 状态,存入队列
this.onFulfilledCallbacks.push(fulfillMicrotask);
this.onRejectedCallbacks.push(rejectMicrotask);
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
value => MyPromise.resolve(onFinally()).then(() => value),
reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
// ... 其他静态方法 all/race 等类似,篇幅所限不全部展开
}
// 处理 then 返回值和 promise2 的关系(Promise 解决过程)
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
let called = false; // 防止多次调用
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
要点解读:
- 立即执行 executor,提供
resolve/reject。 - 状态管理:一旦改变,不能再变。
- 异步回调 :所有
then的回调都用queueMicrotask延迟,确保在解决(resolve/reject)后仍异步。 - 链式与返回值 :
then返回新的 Promise,依据回调返回值x决定新 Promise 状态。resolvePromise函数实现完整的 Promise 解决过程,包括对 thenable 的递归展开和循环引用检查。 - 值穿透 :
then().then().then(v => console.log(v))能正常工作,因为默认函数传值/抛错。
七、Promise 在事件循环中的位置
Promise 的 .then/catch/finally 回调被放入微任务队列(microtask queue)。微任务在每轮宏任务执行完后、下一个宏任务之前清空。
javascript
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1 4 3 2
理解这点对避免异步顺序的坑至关重要。
八、应用场景
- 网络请求:fetch/Axios 都基于 Promise。
- 顺序串行执行 :链式
.then或async/await。 - 并发控制 :
Promise.all处理多个并行任务。 - 竞速与超时 :
Promise.race实现请求超时放弃。 - 错误重试:封装 retry 函数,失败后递归返回 Promise。
- 缓存与共享 :将异步结果保存,多次
.then获取同一个状态。
九、常见坑与避坑指南
1. 忽略错误导致 Promise 静默失败
javascript
// 没有 catch,这个 rejected 将成为未处理的 promise rejection
Promise.reject('error'); // 在 Node 中可能终止进程
解决 :链条末尾加 .catch(),或使用全局 unhandledrejection 监听。
2. then 中忘记 return
javascript
Promise.resolve(1)
.then(v => { v + 1; }) // 没有 return,返回 undefined
.then(console.log); // undefined
3. Promise.all 快速失败导致未完成的请求可能仍运行
Promise.all 一旦 reject,其他请求不会被取消(因为 Promise 无法取消)。若有取消需求,需使用 AbortController(fetch)或外部状态控制。
4. then 中传入非函数参数会被忽略
then('foo') 相当于 then(null),不能处理值,需传入函数。
5. finally 的返回值"陷阱"
javascript
Promise.resolve(10)
.finally(() => 20) // 这里返回的 20 被忽略
.then(console.log); // 10
finally 回调的返回值(非 rejected Promise)不会改变链中的值。
6. 循环中用 async/await 可能失去并发
javascript
[1,2,3].forEach(async (id) => {
await fetchData(id); // 并发执行,但 forEach 不会等它们完成
});
改用 for...of 结合 await 实现顺序,或用 Promise.all(items.map(async ...)) 实现并发等待。
7. Promise.resolve 传递 thenable 可能造成意外的异步
javascript
const thenable = {
then(resolve) { console.log('side effect'); resolve(1); }
};
Promise.resolve(thenable); // 立即输出 'side effect',并异步 resolve
这是特性,但要留意。
8. 滥用 new Promise 包裹已有 Promise
javascript
// 反模式
function fetchData() {
return new Promise((resolve, reject) => {
fetch(url).then(resolve).catch(reject); // 多余,直接 return fetch(url) 即可
});
}
避免不必要的包装,直接返回或链式处理。
十、总结
- Promise 解决了回调地狱,提供链式与统一错误处理。
- 三种状态,一旦敲定,终身不变。
then/catch/finally构建链式流程,静态方法处理集合。- 所有
then回调都在微任务中执行。 - 手写 Promise 时要关注状态、异步、链式、thenable 展开和循环引用。
现在,你可以自信地使用和讲解 Promise 了。如果需要,我还可以展开 async/await 与 Promise 的关系和底层实现。