Promise 的状态机模型:pending → fulfilled/rejected 的不可逆转换
理解 Promise 的基本概念
Promise 是 ES6 引入的一个重要特性,用于更好地处理异步操作。Promise 代表了一个异步操作的最终完成(或失败)及其结果值。Promise 的核心是一个状态机模型,具有三种互斥的状态:pending(待定)、fulfilled(已成功)和 rejected(已失败)。
Promise 的三种状态
javascript
// Promise 的三种状态
const pendingPromise = new Promise(() => {}); // pending 状态
console.log(pendingPromise); // Promise { <pending> }
const fulfilledPromise = Promise.resolve("成功");
console.log(fulfilledPromise); // Promise { '成功' }
const rejectedPromise = Promise.reject(new Error("失败"));
console.log(rejectedPromise); // Promise { <rejected> Error: 失败 }Promise 状态机的详细分析
pending 状态
pending 是 Promise 的初始状态,此时异步操作正在进行中,Promise 既没有被兑现也没有被拒绝。
javascript
const promise = new Promise((resolve, reject) => {
console.log("Promise 开始执行"); // 立即执行
// 在 pending 状态下,可以执行异步操作
setTimeout(() => {
// 这里决定 Promise 的最终状态
resolve("操作完成");
// 或者 reject(new Error("操作失败"));
}, 1000);
});
console.log(promise); // Promise { <pending> }fulfilled 状态
当 Promise 被成功兑现时,它会进入 fulfilled 状态,并携带一个值(value)。
javascript
const fulfilledPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("数据加载成功");
}, 1000);
});
fulfilledPromise.then(result => {
console.log("成功:", result); // "成功: 数据加载成功"
});
// Promise 状态一旦变为 fulfilled,就不能再改变
fulfilledPromise.then(result => {
console.log("再次处理:", result); // "再次处理: 数据加载成功"
});rejected 状态
当 Promise 被拒绝时,它会进入 rejected 状态,并携带一个原因(reason),通常是一个 Error 对象。
javascript
const rejectedPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("网络错误"));
}, 1000);
});
rejectedPromise.catch(error => {
console.log("错误:", error.message); // "错误: 网络错误"
});
// 同样,状态一旦变为 rejected,就不能再改变
rejectedPromise.catch(error => {
console.log("再次处理错误:", error.message); // "再次处理错误: 网络错误"
});Promise 状态的不可逆性
Promise 的状态转换是单向且不可逆的,一旦从 pending 转换为 fulfilled 或 rejected,就不能再改变状态。
状态转换的不可逆性示例
javascript
const promise = new Promise((resolve, reject) => {
console.log("1. Promise 执行器开始执行");
resolve("成功结果");
console.log("2. 已调用 resolve");
// 下面的调用不会改变 Promise 的状态
reject(new Error("失败原因"));
console.log("3. 已调用 reject");
resolve("另一个成功结果");
console.log("4. 再次调用 resolve");
});
promise.then(result => {
console.log("5. 处理结果:", result); // "5. 处理结果: 成功结果"
});
// 输出顺序:
// 1. Promise 执行器开始执行
// 2. 已调用 resolve
// 3. 已调用 reject
// 4. 再次调用 resolve
// 5. 处理结果: 成功结果多次调用 resolve 或 reject
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第一次 resolve");
resolve("第二次 resolve");
reject(new Error("reject 调用"));
}, 100);
});
promise.then(result => {
console.log(result); // "第一次 resolve"
});
// 只有第一次调用有效,后续调用被忽略Promise 状态转换的触发机制
Promise 的状态转换由 resolve 和 reject 函数触发,这两个函数作为参数传递给 Promise 的执行器函数。
resolve 函数的行为
javascript
// 1. resolve 一个普通值
const promise1 = new Promise(resolve => {
resolve(42);
});
promise1.then(value => {
console.log(value); // 42
});
// 2. resolve 另一个 Promise
const promise2 = new Promise(resolve => {
resolve(Promise.resolve("来自另一个 Promise"));
});
promise2.then(value => {
console.log(value); // "来自另一个 Promise"
});
// 3. resolve 一个 thenable 对象
const promise3 = new Promise(resolve => {
resolve({
then: function(onFulfilled) {
onFulfilled("来自 thenable");
}
});
});
promise3.then(value => {
console.log(value); // "来自 thenable"
});reject 函数的行为
javascript
// reject 通常接收一个错误对象
const promise = new Promise((resolve, reject) => {
reject(new Error("操作失败"));
});
promise.catch(error => {
console.log(error.message); // "操作失败"
});
// reject 也可以接收任何值
const promise2 = new Promise((resolve, reject) => {
reject("字符串错误信息");
});
promise2.catch(error => {
console.log(error); // "字符串错误信息"
});Promise 状态机的实际应用
异步操作封装
javascript
// 封装 XMLHttpRequest 为 Promise
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error(`请求失败: ${xhr.status}`));
}
};
xhr.onerror = function() {
reject(new Error('网络错误'));
};
xhr.send();
});
}
// 使用
fetchData('/api/data')
.then(data => console.log('数据:', data))
.catch(error => console.error('错误:', error));多个异步操作的协调
javascript
// 并行执行多个 Promise
function fetchUserData(userId) {
return new Promise(resolve => {
setTimeout(() => resolve({ id: userId, name: `User${userId}` }), 100);
});
}
function fetchUserPosts(userId) {
return new Promise(resolve => {
setTimeout(() => resolve([`Post1 by ${userId}`, `Post2 by ${userId}`]), 200);
});
}
// 所有 Promise 都成功时,整体才成功
Promise.all([
fetchUserData(1),
fetchUserPosts(1)
]).then(([user, posts]) => {
console.log('用户:', user);
console.log('帖子:', posts);
}).catch(error => {
console.error('操作失败:', error);
});链式调用中的状态管理
javascript
// Promise 链中的状态传递
new Promise((resolve, reject) => {
console.log("1. 初始 Promise");
resolve("初始值");
})
.then(value => {
console.log("2. 第一个 then:", value);
return value + " -> 处理后";
})
.then(value => {
console.log("3. 第二个 then:", value);
// 可以返回新的 Promise
return new Promise(resolve => {
setTimeout(() => resolve(value + " -> 异步处理"), 100);
});
})
.then(value => {
console.log("4. 第三个 then:", value);
// 可以抛出错误来改变状态
throw new Error("处理过程中出错");
})
.catch(error => {
console.log("5. 错误处理:", error.message);
// 可以返回值来恢复到 fulfilled 状态
return "错误恢复后的值";
})
.then(value => {
console.log("6. 错误恢复后:", value);
});Promise 状态机的高级特性
Promise.resolve() 和 Promise.reject()
javascript
// Promise.resolve() 创建已成功的 Promise
const resolvedPromise = Promise.resolve("立即成功的值");
resolvedPromise.then(value => {
console.log(value); // "立即成功的值"
});
// Promise.reject() 创建已失败的 Promise
const rejectedPromise = Promise.reject(new Error("立即失败"));
rejectedPromise.catch(error => {
console.log(error.message); // "立即失败"
});状态转换的时机
javascript
console.log("1. 开始");
const promise = new Promise((resolve, reject) => {
console.log("2. Promise 执行器同步执行");
resolve("结果");
console.log("3. resolve 调用后");
});
console.log("4. Promise 创建后");
promise.then(value => {
console.log("6. then 回调:", value);
});
console.log("5. 同步代码结束");
// 输出顺序:
// 1. 开始
// 2. Promise 执行器同步执行
// 3. resolve 调用后
// 4. Promise 创建后
// 5. 同步代码结束
// 6. then 回调: 结果最佳实践
1. 总是处理 Promise 的拒绝状态
javascript
// 好的做法:总是添加 catch 处理
fetchData('/api/data')
.then(data => process(data))
.catch(error => {
console.error('操作失败:', error);
// 可以进行错误恢复或用户提示
});
// 或者在 then 中同时处理成功和失败
fetchData('/api/data')
.then(
data => process(data), // 成功回调
error => console.error('操作失败:', error) // 失败回调
);2. 避免 Promise 状态的意外改变
javascript
// 好的做法:确保 resolve/reject 只被调用一次
function fetchData(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
let isCompleted = false;
xhr.onload = function() {
if (isCompleted) return; // 防止多次调用
isCompleted = true;
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(new Error(`请求失败: ${xhr.status}`));
}
};
xhr.onerror = function() {
if (isCompleted) return; // 防止多次调用
isCompleted = true;
reject(new Error('网络错误'));
};
xhr.send();
});
}3. 合理使用 Promise 链
javascript
// 清晰的 Promise 链
fetchUser(1)
.then(user => fetchUserPosts(user.id))
.then(posts => filterRecentPosts(posts))
.then(recentPosts => displayPosts(recentPosts))
.catch(error => {
console.error('处理用户数据时出错:', error);
showErrorMessage('加载用户数据失败');
});总结
Promise 的状态机模型是其核心特性,通过 pending、fulfilled 和 rejected 三种状态及其不可逆的转换机制,为异步编程提供了清晰和可预测的模式。理解这一模型对于正确使用 Promise、避免常见错误以及编写健壮的异步代码至关重要。Promise 的状态一旦确定就不能改变,这种设计确保了异步操作结果的稳定性和可预测性。