JavaScript Promise 指南:从基础到高级实践
一、Promise 基础概念
1. 什么是 Promise?
Promise 是 JavaScript 中处理异步操作的标准化解决方案,它代表一个尚未完成但预期将来会完成的操作。Promise 对象有三种状态:
- pending(待定):初始状态,既不是成功也不是失败
- fulfilled(已兑现):操作成功完成
- rejected(已拒绝):操作失败
// Promise 基本结构
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 状态变为 fulfilled
} else {
reject(error); // 状态变为 rejected
}
});
2. Promise 的特点
- 不可逆性:状态一旦改变(从 pending 到 fulfilled 或 rejected),就不能再变
- 链式调用:通过
.then()
和.catch()
实现链式调用 - 值穿透:如果 then 中不传函数,值会穿透到后面的 then
- 错误冒泡:错误会一直向后传递,直到被捕获
二、Promise 核心方法
1. 实例方法
.then(onFulfilled, onRejected)
fetchData()
.then(
data => console.log('成功:', data), // 成功回调
error => console.error('失败:', error) // 失败回调(可选)
);
.catch(onRejected)
fetchData()
.then(data => processData(data))
.catch(error => console.error('捕获错误:', error));
.finally(onFinally)
showLoading();
fetchData()
.then(data => render(data))
.catch(error => showError(error))
.finally(() => hideLoading()); // 无论成功失败都会执行
2. 静态方法
Promise.resolve(value)
// 创建一个立即 resolve 的 Promise
Promise.resolve('立即值')
.then(value => console.log(value)); // "立即值"
Promise.reject(reason)
// 创建一个立即 reject 的 Promise
Promise.reject(new Error('失败'))
.catch(err => console.error(err)); // Error: 失败
Promise.all(iterable)
// 等待所有 Promise 完成(全部成功才算成功)
Promise.all([promise1, promise2, promise3])
.then(values => console.log(values)) // [val1, val2, val3]
.catch(error => console.error(error)); // 任一失败立即 reject
Promise.race(iterable)
// 竞速,第一个 settled 的 Promise 决定结果
Promise.race([promise1, promise2])
.then(value => console.log('第一个完成:', value))
.catch(error => console.error('第一个失败:', error));
Promise.allSettled(iterable)
(ES2020)
// 等待所有 Promise 完成,不论成功失败
Promise.allSettled([promise1, promise2])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.error('失败:', result.reason);
}
});
});
三、Promise 高级应用
1. Promise 链式调用
login(user)
.then(token => getUserInfo(token))
.then(userInfo => getOrders(userInfo.id))
.then(orders => renderOrders(orders))
.catch(error => showError(error));
2. Promise 错误处理
// 方式1:每个 then 都处理错误
fetchData()
.then(
data => process(data),
error => handleError(error)
);
// 方式2:统一 catch 处理
fetchData()
.then(data => process(data))
.then(moreData => processMore(moreData))
.catch(error => handleError(error));
3. Promise 与 async/await
async function fetchUserData() {
try {
const token = await login(user);
const userInfo = await getUserInfo(token);
const orders = await getOrders(userInfo.id);
return renderOrders(orders);
} catch (error) {
showError(error);
throw error; // 可以选择继续抛出
}
}
4. 实现 Promise 超时控制
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
)
]);
}
四、Promise 常见面试题
1. 实现一个简单的 Promise
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);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.state === 'pending') {
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) {
// 实现 Promise 解决过程
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}
2. Promise.all 的实现
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
};
3. 如何取消一个 Promise?
JavaScript 原生 Promise 无法直接取消,但可以通过以下模式实现:
function cancellablePromise(executor) {
let rejectFn;
const promise = new Promise((resolve, reject) => {
rejectFn = reject;
executor(resolve, reject);
});
promise.cancel = () => {
rejectFn(new Error('Promise cancelled'));
};
return promise;
}
// 使用示例
const cp = cancellablePromise((resolve, reject) => {
setTimeout(() => resolve('Done'), 2000);
});
cp.then(console.log).catch(console.error);
cp.cancel(); // 取消 Promise
4. 解释 Promise 链中的值穿透
Promise.resolve('foo')
.then(Promise.resolve('bar')) // 这里传入的不是函数!
.then(value => console.log(value)); // 输出什么?
答案:输出 'foo'
。因为 .then()
期望接收函数作为参数,如果传递的不是函数,Promise 会执行"值穿透",直接将上一个 Promise 的值传递下去。
5. 实现一个 Promise 重试机制
function retry(fn, times, delay) {
return new Promise((resolve, reject) => {
const attempt = currentTimes => {
fn()
.then(resolve)
.catch(error => {
if (currentTimes <= 1) {
reject(error);
} else {
setTimeout(() => attempt(currentTimes - 1), delay);
}
});
};
attempt(times);
});
}
// 使用示例
retry(() => fetch('https://api.example.com'), 3, 1000)
.then(console.log)
.catch(console.error);
五、Promise 最佳实践
-
总是返回 Promise:在 then 回调中返回 Promise 或值,确保链式调用
// 好的做法 fetchData() .then(data => process(data)) .then(processed => save(processed)); // 不好的做法 fetchData() .then(data => { process(data); // 没有返回 }) .then(processed => { // processed 是 undefined save(processed); });
-
永远捕获错误:使用
.catch()
或try/catch
处理错误// 方式1 fetchData() .then(process) .catch(handleError); // 方式2 (async/await) try { const data = await fetchData(); const result = await process(data); } catch (error) { handleError(error); }
-
避免 Promise 嵌套:使用链式调用而非嵌套
// 好的做法 fetchData() .then(process) .then(save) .then(log); // 不好的做法 fetchData().then(data => { process(data).then(processed => { save(processed).then(() => { log(); }); }); });
-
合理使用 Promise 静态方法:
Promise.all
:并行执行多个独立操作Promise.race
:超时控制或竞速场景Promise.allSettled
:需要知道所有操作结果时
-
避免同步代码中的 Promise:Promise 设计用于处理异步操作
六、Promise 与事件循环
Promise 的回调作为**微任务(microtask)执行,优先级高于宏任务(macrotask)**如 setTimeout:
console.log('Script start');
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
console.log('Script end');
// 输出顺序:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout
七、Promise 的现代替代方案
虽然 Promise 是处理异步操作的基础,但现代 JavaScript 提供了更高级的抽象:
1. async/await
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
return { user, posts };
} catch (error) {
console.error('Failed to load data:', error);
throw error;
}
}
2. Observable (RxJS)
import { from } from 'rxjs';
from(fetch('https://api.example.com/data'))
.pipe(
map(response => response.json()),
retry(3),
catchError(error => of({ error: true }))
)
.subscribe(data => console.log(data));
总结:Promise 核心要点
- 状态不可逆:pending → fulfilled 或 pending → rejected
- 链式调用:
.then()
返回新 Promise,支持链式操作 - 错误冒泡:错误会一直向后传递,直到被
.catch()
捕获 - 微任务队列:Promise 回调在微任务队列执行,优先级高于宏任务
- 现代异步基础:async/await 基于 Promise 实现
掌握 Promise 的重要性:
Promise 是现代 JavaScript 异步编程的基石,理解 Promise 的工作原理和使用模式,是成为高级 JavaScript 开发者的必备技能。通过合理运用 Promise 链、错误处理和组合方法,可以构建出健壮、可维护的异步代码。