手动实现Promise的实践指南

引言

Promise是JavaScript中处理异步操作的核心机制,它解决了回调地狱问题,提供了更优雅的异步代码编写方式。本文将从Promise的基本原理出发,逐步实现一个符合Promise/A+规范的Promise类,帮助你深入理解Promise的工作机制。

Promise基本原理

Promise的三种状态

Promise有三种状态,分别是:

  • pending:初始状态,既不是成功也不是失败
  • fulfilled:操作成功完成
  • rejected:操作失败

状态只能从pending转换为fulfilled或rejected,且一旦转换完成就不能再改变。

Promise的基本结构

一个基本的Promise实现需要包含:

  1. 状态管理
  2. 回调函数存储
  3. 状态转换逻辑
  4. then方法实现

从零开始实现Promise

1. 基础框架搭建

首先,我们创建一个Promise类,包含基本的状态管理和构造函数:

javascript 复制代码
class MyPromise {
  // 定义状态常量
  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECTED = 'rejected';

  constructor(executor) {
    // 初始状态为pending
    this.status = MyPromise.PENDING;
    // 存储成功的值
    this.value = null;
    // 存储失败的原因
    this.reason = null;
    // 存储成功回调函数队列
    this.onFulfilledCallbacks = [];
    // 存储失败回调函数队列
    this.onRejectedCallbacks = [];

    try {
      // 执行器函数立即执行
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      // 执行器抛出异常时直接 reject
      this.reject(error);
    }
  }

  // 实现resolve方法
  resolve(value) {
    // 只有pending状态才能转换为fulfilled
    if (this.status === MyPromise.PENDING) {
      this.status = MyPromise.FULFILLED;
      this.value = value;
      // 异步执行所有成功回调
      setTimeout(() => {
        this.onFulfilledCallbacks.forEach(callback => {
          callback(this.value);
        });
      }, 0);
    }
  }

  // 实现reject方法
  reject(reason) {
    // 只有pending状态才能转换为rejected
    if (this.status === MyPromise.PENDING) {
      this.status = MyPromise.REJECTED;
      this.reason = reason;
      // 异步执行所有失败回调
      setTimeout(() => {
        this.onRejectedCallbacks.forEach(callback => {
          callback(this.reason);
        });
      }, 0);
    }
  }
}

2. 实现then方法

then方法是Promise的核心,它接收两个参数:成功回调和失败回调,并返回一个新的Promise,实现链式调用:

javascript 复制代码
then(onFulfilled, onRejected) {
  // 确保onFulfilled和onRejected是函数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

  // 返回新的Promise,实现链式调用
  const newPromise = new MyPromise((resolve, reject) => {
    if (this.status === MyPromise.FULFILLED) {
      // 当前状态为fulfilled,异步执行成功回调
      setTimeout(() => {
        try {
          const result = onFulfilled(this.value);
          // 处理回调返回值,实现链式传递
          this.resolvePromise(newPromise, result, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }, 0);
    }

    if (this.status === MyPromise.REJECTED) {
      // 当前状态为rejected,异步执行失败回调
      setTimeout(() => {
        try {
          const result = onRejected(this.reason);
          this.resolvePromise(newPromise, result, resolve, reject);
        } catch (error) {
          reject(error);
        }
      }, 0);
    }

    if (this.status === MyPromise.PENDING) {
      // 当前状态为pending,将回调存入队列
      this.onFulfilledCallbacks.push(() => {
        setTimeout(() => {
          try {
            const result = onFulfilled(this.value);
            this.resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      });

      this.onRejectedCallbacks.push(() => {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            this.resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      });
    }
  });

  return newPromise;
}

3. 实现resolvePromise方法

resolvePromise方法用于处理回调函数返回值,实现Promise链式调用的核心逻辑:

javascript 复制代码
resolvePromise(promise, result, resolve, reject) {
  // 如果返回值是当前Promise,抛出类型错误
  if (promise === result) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // 标记是否已经调用过resolve或reject
  let called = false;

  // 如果返回值是Promise实例
  if (result instanceof MyPromise) {
    if (result.status === MyPromise.PENDING) {
      // 如果返回的Promise还是pending状态,等待其状态变化
      result.then(value => {
        this.resolvePromise(promise, value, resolve, reject);
      }, reason => {
        reject(reason);
      });
    } else {
      // 如果返回的Promise已经有结果,直接使用其结果
      result.then(resolve, reject);
    }
  } 
  // 如果返回值是对象或函数(可能是thenable对象)
  else if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
    try {
      // 尝试获取then方法
      const then = result.then;
      if (typeof then === 'function') {
        // 如果是thenable对象,调用其then方法
        then.call(result, value => {
          if (called) return;
          called = true;
          this.resolvePromise(promise, value, resolve, reject);
        }, reason => {
          if (called) return;
          called = true;
          reject(reason);
        });
      } else {
        // 如果不是thenable对象,直接resolve
        resolve(result);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    // 如果返回值是基本类型,直接resolve
    resolve(result);
  }
}

4. 实现catch方法

catch方法是then方法的语法糖,用于捕获Promise的错误:

javascript 复制代码
catch(onRejected) {
  return this.then(null, onRejected);
}

5. 实现finally方法

finally方法无论Promise状态如何都会执行,并且会将结果透传给下一个then:

javascript 复制代码
finally(onFinally) {
  return this.then(
    value => MyPromise.resolve(onFinally()).then(() => value),
    reason => MyPromise.resolve(onFinally()).then(() => { throw reason })
  );
}

6. 实现静态方法resolve

Promise.resolve方法返回一个以给定值解析后的Promise对象:

javascript 复制代码
static resolve(value) {
  // 如果value已经是Promise实例,直接返回
  if (value instanceof MyPromise) {
    return value;
  }
  
  // 如果value是thenable对象,转换为Promise
  if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
    try {
      const then = value.then;
      if (typeof then === 'function') {
        return new MyPromise(then.bind(value));
      }
    } catch (error) {
      return new MyPromise((resolve, reject) => reject(error));
    }
  }
  
  // 基本类型值直接返回resolved状态的Promise
  return new MyPromise(resolve => resolve(value));
}

7. 实现静态方法reject

Promise.reject方法返回一个带有拒绝原因的Promise对象:

javascript 复制代码
static reject(reason) {
  return new MyPromise((resolve, reject) => reject(reason));
}

8. 实现静态方法all

Promise.all方法接收一个Promise数组,返回一个新的Promise,当所有Promise都成功时才成功,有一个失败则立即失败:

javascript 复制代码
static all(promises) {
  return new MyPromise((resolve, reject) => {
    // 检查输入是否是数组
    if (!Array.isArray(promises)) {
      return reject(new TypeError('The input must be an array'));
    }

    const results = [];
    let completed = 0;
    const total = promises.length;

    // 如果传入空数组,直接resolve
    if (total === 0) {
      return resolve(results);
    }

    promises.forEach((promise, index) => {
      // 使用Promise.resolve处理非Promise值
      MyPromise.resolve(promise).then(value => {
        results[index] = value;
        completed++;
        
        // 所有Promise都完成时resolve
        if (completed === total) {
          resolve(results);
        }
      }, reason => {
        // 有一个Promise失败就立即reject
        reject(reason);
      });
    });
  });
}

9. 实现静态方法race

Promise.race方法接收一个Promise数组,返回一个新的Promise,其结果由第一个完成的Promise决定:

javascript 复制代码
static race(promises) {
  return new MyPromise((resolve, reject) => {
    // 检查输入是否是数组
    if (!Array.isArray(promises)) {
      return reject(new TypeError('The input must be an array'));
    }

    // 如果传入空数组,保持pending状态
    if (promises.length === 0) {
      return;
    }

    promises.forEach(promise => {
      // 使用Promise.resolve处理非Promise值
      MyPromise.resolve(promise).then(resolve, reject);
    });
  });
}

10. 实现静态方法allSettled

Promise.allSettled方法等待所有Promise完成(无论成功或失败),并返回每个Promise的结果:

javascript 复制代码
static allSettled(promises) {
  return new MyPromise((resolve) => {
    // 检查输入是否是数组
    if (!Array.isArray(promises)) {
      return resolve([{ status: 'rejected', reason: new TypeError('The input must be an array') }]);
    }

    const results = [];
    let completed = 0;
    const total = promises.length;

    // 如果传入空数组,直接resolve
    if (total === 0) {
      return resolve(results);
    }

    promises.forEach((promise, index) => {
      MyPromise.resolve(promise)
        .then(value => {
          results[index] = { status: 'fulfilled', value };
        })
        .catch(reason => {
          results[index] = { status: 'rejected', reason };
        })
        .finally(() => {
          completed++;
          if (completed === total) {
            resolve(results);
          }
        });
    });
  });
}

完整代码实现

将以上所有部分整合,得到完整的Promise实现:

javascript 复制代码
class MyPromise {
  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECTED = 'rejected';

  constructor(executor) {
    this.status = MyPromise.PENDING;
    this.value = null;
    this.reason = null;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }

  resolve(value) {
    if (this.status === MyPromise.PENDING) {
      this.status = MyPromise.FULFILLED;
      this.value = value;
      setTimeout(() => {
        this.onFulfilledCallbacks.forEach(callback => {
          callback(this.value);
        });
      }, 0);
    }
  }

  reject(reason) {
    if (this.status === MyPromise.PENDING) {
      this.status = MyPromise.REJECTED;
      this.reason = reason;
      setTimeout(() => {
        this.onRejectedCallbacks.forEach(callback => {
          callback(this.reason);
        });
      }, 0);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    const newPromise = new MyPromise((resolve, reject) => {
      if (this.status === MyPromise.FULFILLED) {
        setTimeout(() => {
          try {
            const result = onFulfilled(this.value);
            this.resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === MyPromise.REJECTED) {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            this.resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === MyPromise.PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onFulfilled(this.value);
              this.resolvePromise(newPromise, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onRejected(this.reason);
              this.resolvePromise(newPromise, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return newPromise;
  }

  resolvePromise(promise, result, resolve, reject) {
    if (promise === result) {
      return reject(new TypeError('Chaining cycle detected for promise'));
    }

    let called = false;

    if (result instanceof MyPromise) {
      if (result.status === MyPromise.PENDING) {
        result.then(value => {
          this.resolvePromise(promise, value, resolve, reject);
        }, reason => {
          reject(reason);
        });
      } else {
        result.then(resolve, reject);
      }
    } else if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
      try {
        const then = result.then;
        if (typeof then === 'function') {
          then.call(
            result,
            value => {
              if (called) return;
              called = true;
              this.resolvePromise(promise, value, resolve, reject);
            },
            reason => {
              if (called) return;
              called = true;
              reject(reason);
            }
          );
        } else {
          resolve(result);
        }
      } catch (error) {
        if (called) return;
        called = true;
        reject(error);
      }
    } else {
      resolve(result);
    }
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(onFinally) {
    return this.then(
      value => MyPromise.resolve(onFinally()).then(() => value),
      reason => MyPromise.resolve(onFinally()).then(() => { throw reason })
    );
  }

  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    
    if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
      try {
        const then = value.then;
        if (typeof then === 'function') {
          return new MyPromise(then.bind(value));
        }
      } catch (error) {
        return new MyPromise((resolve, reject) => reject(error));
      }
    }
    
    return new MyPromise(resolve => resolve(value));
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason));
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      if (!Array.isArray(promises)) {
        return reject(new TypeError('The input must be an array'));
      }

      const results = [];
      let completed = 0;
      const total = promises.length;

      if (total === 0) {
        return resolve(results);
      }

      promises.forEach((promise, index) => {
        MyPromise.resolve(promise).then(value => {
          results[index] = value;
          completed++;
          if (completed === total) {
            resolve(results);
          }
        }, reason => {
          reject(reason);
        });
      });
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      if (!Array.isArray(promises)) {
        return reject(new TypeError('The input must be an array'));
      }

      if (promises.length === 0) {
        return;
      }

      promises.forEach(promise => {
        MyPromise.resolve(promise).then(resolve, reject);
      });
    });
  }

  static allSettled(promises) {
    return new MyPromise((resolve) => {
      if (!Array.isArray(promises)) {
        return resolve([{ status: 'rejected', reason: new TypeError('The input must be an array') }]);
      }

      const results = [];
      let completed = 0;
      const total = promises.length;

      if (total === 0) {
        return resolve(results);
      }

      promises.forEach((promise, index) => {
        MyPromise.resolve(promise)
          .then(value => {
            results[index] = { status: 'fulfilled', value };
          })
          .catch(reason => {
            results[index] = { status: 'rejected', reason };
          })
          .finally(() => {
            completed++;
            if (completed === total) {
              resolve(results);
            }
          });
      });
    });
  }
}

Promise使用示例

基本用法

javascript 复制代码
// 创建Promise
const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Success');
    // 或者 reject(new Error('Failed'));
  }, 1000);
});

// 使用Promise
promise
  .then(value => {
    console.log('成功:', value);
    return value + ' - 链式调用';
  })
  .then(value => {
    console.log('链式调用结果:', value);
  })
  .catch(reason => {
    console.log('失败:', reason);
  })
  .finally(() => {
    console.log('无论成功失败都会执行');
  });

静态方法使用示例

javascript 复制代码
// Promise.all
const promise1 = MyPromise.resolve(1);
const promise2 = MyPromise.resolve(2);
const promise3 = MyPromise.resolve(3);

MyPromise.all([promise1, promise2, promise3])
  .then(values => console.log('all results:', values)) // [1, 2, 3]
  .catch(reason => console.log('all error:', reason));

// Promise.race
const promiseA = new MyPromise(resolve => setTimeout(() => resolve('A'), 100));
const promiseB = new MyPromise(resolve => setTimeout(() => resolve('B'), 50));

MyPromise.race([promiseA, promiseB])
  .then(value => console.log('race result:', value)) // 'B'
  .catch(reason => console.log('race error:', reason));

// Promise.allSettled
const promiseX = MyPromise.resolve('X');
const promiseY = MyPromise.reject(new Error('Y failed'));

MyPromise.allSettled([promiseX, promiseY])
  .then(results => console.log('allSettled results:', results));
  // [
  //   { status: 'fulfilled', value: 'X' },
  //   { status: 'rejected', reason: Error('Y failed') }
  // ]

Promise/A+规范测试

为了验证我们实现的Promise是否符合标准,可以使用Promise/A+规范测试套件:

javascript 复制代码
// 安装测试套件
// npm install promises-aplus-tests -D

// 添加测试文件promise-test.js
const promisesAplusTests = require('promises-aplus-tests');
const adapter = {
  deferred() {
    const promise = new MyPromise((resolve, reject) => {
      return {
        resolve,
        reject
      };
    });
    return {
      promise,
      resolve: promise.resolve,
      reject: promise.reject
    };
  }
};

promisesAplusTests(adapter, function (err) {
  // 所有测试完成
  console.log(err);
});

常见问题与注意事项

1. Promise状态不可逆

一旦Promise状态从pending变为fulfilled或rejected,就不能再改变:

javascript 复制代码
const promise = new MyPromise((resolve, reject) => {
  resolve('Success');
  reject(new Error('Failed')); // 此调用无效
});

promise.then(value => console.log(value)); // 'Success'

2. 异步执行特性

Promise的回调函数是异步执行的,即使立即resolve:

javascript 复制代码
console.log('Start');
const promise = new MyPromise(resolve => {
  console.log('Executor');
  resolve('Success');
});
promise.then(value => console.log(value));
console.log('End');

// 输出顺序:
// Start
// Executor
// End
// Success

3. 错误冒泡

Promise链中的错误会一直向后传递,直到被catch捕获:

javascript 复制代码
new MyPromise((resolve, reject) => {
  resolve();
})
.then(() => {
  throw new Error('Error in then');
})
.then(() => {
  console.log('This will not execute');
})
.catch(reason => {
  console.log('Catch error:', reason); // 捕获前面抛出的错误
});

总结

通过手动实现Promise,我们深入理解了其内部工作机制,包括状态管理、异步处理、链式调用等核心概念。Promise的设计思想非常优雅,它通过将回调函数规范化,解决了异步代码的可读性和可维护性问题。

掌握Promise不仅有助于我们更好地使用JavaScript异步编程,也能帮助我们理解更高级的异步模式,如async/await。希望本文能帮助你彻底掌握Promise,并在实际项目中灵活运用。

扩展学习

  1. async/await语法糖与Promise的关系
  2. Promise性能优化技巧
  3. 手写Promise并发控制
  4. Promise在实际项目中的最佳实践
  5. 浏览器与Node.js中的Promise实现差异
相关推荐
Arvin62735 分钟前
Nginx IP授权页面实现步骤
服务器·前端·nginx
xw52 小时前
Trae安装指定版本的插件
前端·trae
默默地离开2 小时前
前端开发中的 Mock 实践与接口联调技巧
前端·后端·设计模式
南岸月明2 小时前
做副业,稳住心态,不靠鸡汤!我的实操经验之路
前端
嘗_2 小时前
暑期前端训练day7——有关vue-diff算法的思考
前端·vue.js·算法
MediaTea3 小时前
Python 库手册:html.parser HTML 解析模块
开发语言·前端·python·html
杨荧3 小时前
基于爬虫技术的电影数据可视化系统 Python+Django+Vue.js
开发语言·前端·vue.js·后端·爬虫·python·信息可视化
BD_Marathon3 小时前
IDEA中创建Maven Web项目
前端·maven·intellij-idea
waillyer3 小时前
taro跳转路由取值
前端·javascript·taro
凌辰揽月3 小时前
贴吧项目总结二
java·前端·css·css3·web