JavaScript Promise 的三种状态——pending、fulfilled、rejected,配合链式调用特性,天生适合组织异步流程。再加上 Promise.withResolvers 这个解耦工具,结合现代 UI 框架,即可实现声明式的状态渲染。这个概念听起来可能有些抽象,但一旦理解了这个思路,你会发现许多状态管理难题将迎刃而解。

有人可能会认为 Promise 本身并非专门的状态管理库。然而,它天然适用于表达和流转那些“一次性、不可逆”的状态变化,例如“请求中→成功”或“请求中→失败”。只需善用其三种状态与链式特性,无需引入额外框架,即可将异步流程的状态逻辑梳理得清晰明了。
用 Promise 封装单次状态流转
许多场景的本质就是“等待一个结果”:点击按钮后等待响应、监听一次事件、获取一次配置。这类操作只需一次决议,恰好与 Promise 的语义完美匹配:
- 创建时处于 pending 状态,天然代表“进行中”
- 调用 resolve(value) 时,Promise 状态变为 fulfilled,并携带结果;调用 reject(error) 则变为 rejected,携带错误信息
- 状态一旦确定即不可逆,可避免重复触发或覆盖,有效减少竞态风险
举例来说,若要封装一个函数等待 DOM 元素被首次点击,传统做法需维护一个 flag 变量,而使用 Promise 则简单得多:
function waitForFirstClick(el) {
const { promise, resolve } = Promise.withResolvers();
const handler = () => {
el.removeEventListener('click', handler);
resolve('clicked');
};
el.addEventListener('click', handler);
return promise;
}调用方只需关注这个 promise,无需手动维护 flag 或回调队列,代码简洁清晰。
用 Promise 链控制状态顺序
若需按顺序执行多个异步操作,例如先登录、再拉取用户信息、最后加载偏好设置,Promise 链就是最直接的选择:
- 每个 .then() 接收上一环节的 fulfilled 结果,自然形成一条“成功路径”
- .catch() 可捕获任意环节的 rejected,统一处理失败分支
- 链中任意环节返回新的 Promise,即可嵌套更复杂的状态分支
这样一来,无需手动编写状态机或使用全局变量标记“当前进度”。状态的推进由执行时机自动保证,清晰可靠。
用 Promise.withResolvers 解耦决议责任
传统上,使用 new Promise 时需传入一个 executor 函数,将 resolve 和 reject 锁死在内部作用域,这并不总是方便。而 Promise.withResolvers 返回一个包含 promise、resolve、reject 的三元组,将“创建”与“决议”职责分离:
- 主函数返回 promise,专注于定义“要等待什么”
- 事件处理器、定时器回调、外部 API 响应等持有 resolve/reject,专注于“何时满足条件”
- 配合 WeakMap 可按 ID 关联 resolver,有效避免内存泄漏
此模式特别适合插件系统、跨模块通信、测试模拟等需要延迟决议的场景,让代码组织更灵活。
结合 UI 框架做声明式状态渲染
在 Vue 或 React 等 UI 框架中,Promise 实例可直接作为响应式数据源。无需手动维护 loading、error、data 三个变量,而是让 Promise 本身驱动 UI 切换:
- 在 Vue 中,可用 v-if/v-else 区分 pending、fulfilled、rejected 状态
- 借助 vue-prom 等组件,可用插槽自动匹配不同状态下的 UI
- 在 React 中,可通过 useEffect 加 useState 跟踪 promise 状态,或使用 useAsync 等自定义 Hook
将“加载中”、“成功展示”、“报错提示”等 UI 状态直接映射到 Promise 的内在状态,可大幅减少样板代码。这才是真正的简化——让状态管理回归本质,而非用一堆变量去模拟它。
