Skip to content

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 的状态转换由 resolvereject 函数触发,这两个函数作为参数传递给 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 的状态一旦确定就不能改变,这种设计确保了异步操作结果的稳定性和可预测性。