Promise 详解与实现:从原理到实践
一、Promise 是什么?
Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个异步操作的最终完成(或失败)及其结果值。Promise 解决了传统回调函数嵌套("回调地狱")的问题,提供了更优雅的异步代码组织方式。
Promise 的三种状态:
pending
:初始状态,既不是成功也不是失败fulfilled
:操作成功完成rejected
:操作失败
状态特点:一旦从 pending
转变为 fulfilled
或 rejected
,状态就会凝固,不会再发生变化。
二、Promise 核心特性
- 链式调用:通过
.then()
方法实现,避免回调嵌套 - 错误捕获:通过
.catch()
统一捕获异步操作中的错误 - 状态不可逆:一旦状态改变,就会永久保持该状态
- 异步穿透:如果
.then()
中没有传入处理函数,会将结果传递给下一个.then()
三、简易 Promise 实现
下面我们实现一个具备核心功能的 Promise:
class MyPromise {// 构造函数接收一个执行器函数constructor(executor) {// 初始状态为pendingthis.status = 'pending';// 成功的值this.value = undefined;// 失败的原因this.reason = undefined;// 成功回调函数队列this.onFulfilledCallbacks = [];// 失败回调函数队列this.onRejectedCallbacks = [];// 成功执行函数const resolve = (value) => {// 只有pending状态才能改变if (this.status === 'pending') {this.status = 'fulfilled';this.value = value;// 执行所有成功回调this.onFulfilledCallbacks.forEach(fn => fn());}};// 失败执行函数const reject = (reason) => {// 只有pending状态才能改变if (this.status === 'pending') {this.status = 'rejected';this.reason = reason;// 执行所有失败回调this.onRejectedCallbacks.forEach(fn => fn());}};// 立即执行执行器函数,并捕获可能的错误try {executor(resolve, reject);} catch (error) {reject(error);}}// then方法接收成功和失败回调then(onFulfilled, onRejected) {// 处理没有传回调函数的情况onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };// 创建新的Promise实现链式调用const promise2 = new MyPromise((resolve, reject) => {// 状态为成功时执行if (this.status === 'fulfilled') {// 使用setTimeout模拟异步执行setTimeout(() => {try {const x = onFulfilled(this.value);// 处理返回值,实现链式传递this.resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}}, 0);}// 状态为失败时执行if (this.status === 'rejected') {setTimeout(() => {try {const x = onRejected(this.reason);this.resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}}, 0);}// 状态为pending时,将回调存入队列if (this.status === 'pending') {this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {const x = onFulfilled(this.value);this.resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}}, 0);});this.onRejectedCallbacks.push(() => {setTimeout(() => {try {const x = onRejected(this.reason);this.resolvePromise(promise2, x, resolve, reject);} catch (error) {reject(error);}}, 0);});}});return promise2;}// 处理then方法返回值的工具函数resolvePromise(promise2, x, resolve, reject) {// 如果返回的是当前promise实例,抛出循环引用错误if (promise2 === x) {return reject(new TypeError('Chaining cycle detected for promise'));}// 防止多次调用let called = false;// 如果x是对象或函数if (x !== null && (typeof x === 'object' || typeof x === 'function')) {try {// 尝试获取then方法const then = x.then;if (typeof then === 'function') {// 如果then是函数,将其视为Promisethen.call(x,y => {if (called) return;called = true;// 递归处理this.resolvePromise(promise2, y, resolve, reject);},r => {if (called) return;called = true;reject(r);});} else {// 不是Promise,直接 resolveresolve(x);}} catch (error) {if (called) return;called = true;reject(error);}} else {// 基本类型,直接 resolveresolve(x);}}// catch方法,用于捕获错误catch(onRejected) {return this.then(null, onRejected);}// 静态resolve方法static resolve(value) {return new MyPromise((resolve) => {resolve(value);});}// 静态reject方法static reject(reason) {return new MyPromise((_, reject) => {reject(reason);});}
}
四、Promise 常用方法解析
Promise.prototype.then()
- 作用:为 Promise 实例添加状态改变时的回调函数
- 返回值:一个新的 Promise 实例,实现链式调用
Promise.prototype.catch()
- 作用:用于指定发生错误时的回调函数
- 本质:
.then(null, onRejected)
的语法糖
Promise.resolve()
- 作用:将现有对象转为 Promise 对象
- 特殊规则:如果参数是 Promise 实例,会原封不动地返回
Promise.reject()
- 作用:返回一个状态为
rejected
的 Promise 实例
- 作用:返回一个状态为
Promise.all()
- 作用:接收一个 Promise 数组,只有所有 Promise 都成功才成功,有一个失败就失败
Promise.race()
- 作用:接收一个 Promise 数组,返回第一个改变状态的 Promise 的结果
五、Promise 实际应用场景
- 异步操作串行执行
// 按顺序加载三个资源
fetchData1().then(data1 => fetchData2(data1)).then(data2 => fetchData3(data2)).then(result => console.log(result)).catch(error => console.error(error));
- 异步操作并行执行
// 同时加载多个资源,全部完成后处理
Promise.all([fetchData1(), fetchData2(), fetchData3()]).then(results => {console.log('所有数据加载完成', results);}).catch(error => {console.error('有一个请求失败', error);});
- 与 async/await 结合使用
async function handleData() {try {const data1 = await fetchData1();const data2 = await fetchData2(data1);const result = await fetchData3(data2);return result;} catch (error) {console.error(error);}
}
六、Promise 常见面试题
Promise 有哪些优缺点?
- 优点:解决回调地狱、链式调用清晰、统一错误处理
- 缺点:无法取消、错误需要主动捕获、处于 pending 状态时无法得知进展
Promise 中的 then 方法返回的是什么?
- 返回一个新的 Promise 对象,这是链式调用的基础
如何实现 Promise 并行限制?
- 可以实现一个
Promise.map
方法,控制同时运行的 Promise 数量
- 可以实现一个
Promise 构造函数是同步执行还是异步执行?
- 构造函数中的代码是同步执行的,而
then
方法中的回调是异步执行的
- 构造函数中的代码是同步执行的,而
七、总结
Promise 是 JavaScript 异步编程的重要里程碑,它解决了传统回调模式的诸多问题,为异步代码提供了更优雅的组织方式。理解 Promise 的工作原理不仅能帮助我们更好地使用它,还能提升对 JavaScript 异步机制的理解。
随着 ES7 中 async/await 的出现,我们有了更简洁的异步编程方式,但 async/await 本质上还是基于 Promise 实现的语法糖。