1. 为什么我们需要Promise?
2009年,Node.js的诞生让JavaScript正式进入后端开发领域。随着前端复杂度的不断提升,回调地狱(Callback Hell)成为每个前端开发者心中的痛。想象一下,当你需要依次执行三个异步操作时,代码会变成这样:
javascript复制getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
getMoreData(c, function(d){
getMoreData(d, function(e){
// 终于拿到最终数据了...
});
});
});
});
});
这种金字塔式的代码结构不仅难以阅读和维护,错误处理更是噩梦。ES6引入的Promise正是为了解决这些问题而生。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Promise核心原理解析
2.1 Promise的三种状态
每个Promise对象都处于以下三种状态之一:
- pending:初始状态,既不是成功也不是失败
- fulfilled:操作成功完成
- rejected:操作失败
状态转换是不可逆的:一旦从pending变为fulfilled或rejected,状态就固定了。
2.2 Promise的基本结构
javascript复制const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 成功 */) {
resolve(value); // 状态变为fulfilled
} else {
reject(error); // 状态变为rejected
}
});
重要提示:Promise构造函数中的执行器函数(executor)是同步执行的,但then/catch回调是异步的。
3. Promise的链式调用艺术
3.1 基础链式调用
javascript复制fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
return processData(data);
})
.then(processedData => {
console.log(processedData);
})
.catch(error => {
console.error('Error:', error);
});
3.2 链式调用的四个黄金法则
- 每次
.then()都会返回一个新的Promise - 如果回调函数返回非Promise值,它会被包装成已解决的Promise
- 如果回调函数抛出异常,返回的Promise会被拒绝
- 前一个
.then()返回的Promise会决定下一个.then()的执行
3.3 值穿透现象
javascript复制Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log); // 输出1
这是因为.then()的参数如果不是函数,会发生"值穿透"——值会直接传递给下一个.then()。
4. 高级Promise模式
4.1 Promise组合方法
Promise.all()
javascript复制const [user, posts] = await Promise.all([
fetch('/user'),
fetch('/posts')
]);
注意:如果其中任何一个Promise被拒绝,整个Promise.all()会立即拒绝。
Promise.race()
javascript复制const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), 5000);
});
const data = await Promise.race([
fetch('/api'),
timeout
]);
Promise.allSettled()
javascript复制const results = await Promise.allSettled([
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
]);
/*
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: 'error' },
{ status: 'fulfilled', value: 3 }
]
*/
4.2 取消Promise的模式
虽然Promise本身不支持取消,但我们可以通过包装实现:
javascript复制function cancellablePromise(promise) {
let cancel;
const wrappedPromise = new Promise((resolve, reject) => {
cancel = reject;
promise.then(resolve, reject);
});
return {
promise: wrappedPromise,
cancel: () => cancel(new Error('Promise cancelled'))
};
}
const { promise, cancel } = cancellablePromise(fetch('/api'));
// 需要取消时调用cancel()
5. 错误处理全攻略
5.1 同步错误 vs 异步错误
javascript复制// 同步错误 - 能被try/catch捕获
try {
throw new Error('sync error');
} catch (e) {
console.log('Caught:', e);
}
// 异步错误 - 不能被try/catch捕获
try {
Promise.reject(new Error('async error'));
} catch (e) {
console.log('Will not catch:', e); // 不会执行
}
5.2 全局Promise错误捕获
javascript复制// 浏览器环境
window.addEventListener('unhandledrejection', event => {
console.warn('Unhandled rejection:', event.reason);
event.preventDefault(); // 阻止默认错误输出
});
// Node.js环境
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
5.3 最佳错误处理实践
- 总是为Promise链添加
.catch()处理 - 在async函数中使用try/catch
- 避免在Promise构造函数中使用try/catch(应该用reject)
- 区分操作错误(应该捕获)和编程错误(应该修复)
6. Promise与async/await的完美配合
6.1 基本转换
javascript复制// Promise风格
function fetchData() {
return fetch('/api')
.then(response => response.json())
.then(data => processData(data));
}
// async/await风格
async function fetchData() {
const response = await fetch('/api');
const data = await response.json();
return processData(data);
}
6.2 并行执行优化
javascript复制// 顺序执行 - 慢
async function sequential() {
const a = await fetchA();
const b = await fetchB();
return { a, b };
}
// 并行执行 - 快
async function parallel() {
const [a, b] = await Promise.all([fetchA(), fetchB()]);
return { a, b };
}
6.3 常见陷阱
- 忘记await:导致后续代码在Promise解决前执行
- 过度顺序化:本可并行的操作被写成顺序执行
- 错误处理遗漏:忘记用try/catch包裹await
7. Promise性能优化技巧
7.1 内存泄漏预防
javascript复制// 错误示例 - 可能导致内存泄漏
function leakyFunction() {
const hugeArray = new Array(1000000).fill('*');
return new Promise(resolve => {
setTimeout(() => resolve(hugeArray.length), 1000);
});
}
// 正确做法 - 使用后释放引用
function safeFunction() {
return new Promise(resolve => {
const hugeArray = new Array(1000000).fill('*');
setTimeout(() => {
resolve(hugeArray.length);
hugeArray = null; // 释放引用
}, 1000);
});
}
7.2 批量处理技巧
javascript复制// 低效 - 逐个处理
async function processItems(items) {
const results = [];
for (const item of items) {
results.push(await processItem(item));
}
return results;
}
// 高效 - 批量处理
async function processItems(items, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(processItem));
results.push(...batchResults);
}
return results;
}
8. 实战:构建Promise工具库
8.1 重试机制实现
javascript复制function retry(fn, retries = 3, delay = 1000) {
return new Promise((resolve, reject) => {
const attempt = (n) => {
fn().then(resolve)
.catch(error => {
if (n === 0) {
reject(error);
} else {
console.log(`Retry ${retries - n + 1}/${retries}`);
setTimeout(() => attempt(n - 1), delay);
}
});
};
attempt(retries);
});
}
// 使用示例
retry(() => fetch('/unstable-api'), 5, 2000)
.then(console.log)
.catch(console.error);
8.2 超时控制增强版
javascript复制function timeoutPromise(promise, timeout, error = 'Timeout') {
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(error)), timeout);
});
return Promise.race([promise, timeoutPromise])
.finally(() => clearTimeout(timer));
}
// 使用示例
timeoutPromise(fetch('/slow-api'), 5000)
.then(console.log)
.catch(console.error);
9. Promise面试精要
9.1 高频面试题解析
题目1:以下代码的输出顺序是什么?
javascript复制console.log('start');
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));
setTimeout(() => console.log('timeout'), 0);
console.log('end');
答案:start → end → promise1 → promise2 → timeout
题目2:实现一个限制并发数的Promise调度器
javascript复制class PromiseScheduler {
constructor(max) {
this.max = max;
this.queue = [];
this.running = 0;
}
add(promiseCreator) {
return new Promise((resolve, reject) => {
this.queue.push({
promiseCreator,
resolve,
reject
});
this.run();
});
}
run() {
while (this.running < this.max && this.queue.length) {
const { promiseCreator, resolve, reject } = this.queue.shift();
this.running++;
promiseCreator()
.then(resolve, reject)
.finally(() => {
this.running--;
this.run();
});
}
}
}
// 使用示例
const scheduler = new PromiseScheduler(2);
const timeout = (time) => () => new Promise(resolve => setTimeout(resolve, time));
scheduler.add(timeout(1000)).then(() => console.log(1));
scheduler.add(timeout(500)).then(() => console.log(2));
scheduler.add(timeout(300)).then(() => console.log(3));
scheduler.add(timeout(400)).then(() => console.log(4));
// 输出顺序:2 3 1 4
9.2 Promise实现原理
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') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
10. 现代前端中的Promise实践
10.1 在React中的应用
javascript复制function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(data => {
setUser(data);
setError(null);
})
.catch(err => {
setError(err.message);
setUser(null);
})
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <Profile data={user} />;
}
10.2 在Node.js中的优化
javascript复制// 使用util.promisify转换回调风格函数
const { promisify } = require('util');
const fs = require('fs');
const readFile = promisify(fs.readFile);
// 使用async/await处理多个异步操作
async function processFiles() {
try {
const [file1, file2] = await Promise.all([
readFile('file1.txt', 'utf8'),
readFile('file2.txt', 'utf8')
]);
const result = await processData(file1, file2);
await writeFile('result.txt', result);
} catch (err) {
console.error('Error:', err);
}
}
10.3 与Web Worker的集成
javascript复制// main.js
const worker = new Worker('worker.js');
function workerPromise(message) {
return new Promise((resolve, reject) => {
worker.onmessage = ({ data }) => {
if (data.error) {
reject(data.error);
} else {
resolve(data.result);
}
};
worker.postMessage(message);
});
}
// 使用示例
workerPromise({ task: 'heavyCalculation', data: largeDataSet })
.then(result => console.log('Result:', result))
.catch(error => console.error('Error:', error));
// worker.js
self.onmessage = ({ data }) => {
try {
const result = performHeavyCalculation(data);
self.postMessage({ result });
} catch (error) {
self.postMessage({ error: error.message });
}
};
在实际项目中,我发现Promise的最佳实践是:永远为每个Promise链添加错误处理,即使是那些"理论上不会出错"的操作。我曾经因为一个未被捕获的Promise拒绝导致整个应用静默失败,花了整整两天才找到问题根源。现在我的原则是:没有.catch()的Promise就像没有安全带的赛车——速度再快也危险。
