··18 阅读·预计 21 分钟

JavaScript Promise 完全指南:从原理到手写实现

Promise 是 JavaScript 异步编程的核心。本文从规范出发,深入理解 Promise 的运行机制,并手写一个符合 Promise/A+ 规范的实现。

一、为什么需要 Promise

回调地狱的问题

// 回调地狱:代码难以阅读和维护
getUser(userId, function(user) {
  getOrders(user.id, function(orders) {
    getOrderDetail(orders[0].id, function(detail) {
      // 嵌套越来越深...
    });
  });
});

Promise 解决了什么

  1. 链式调用 - 扁平化异步流程
  2. 错误处理 - 统一的 catch 机制
  3. 状态管理 - 明确的 pending/fulfilled/rejected 三种状态

二、Promise 核心概念

三种状态

const promise = new Promise((resolve, reject) => {
  // pending: 初始状态
  
  // fulfilled: 操作成功
  resolve(value);
  
  // 或 rejected: 操作失败
  reject(reason);
});

关键特性

  • 状态只能改变一次(pending → fulfilled 或 pending → rejected)
  • 改变后不可逆,称为 resolved(已定型)

then 方法的核心规则

promise.then(onFulfilled, onRejected);
  1. onFulfilledonRejected 是可选参数
  2. 如果不是函数,必须被忽略
  3. onFulfilled 在 fulfilled 状态调用,参数是 value
  4. onRejected 在 rejected 状态调用,参数是 reason
  5. then 必须返回一个新的 Promise

值穿透

Promise.resolve(1)
  .then()           // 值穿透:跳过非函数参数
  .then()           // 继续穿透
  .then(val => {
    console.log(val); // 1
  });

三、手写 Promise 实现

基础结构

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    
    // 存储回调(因为可能异步 resolve)
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
    
    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 执行所有成功回调
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };
    
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 执行所有失败回调
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };
    
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }
}

then 方法实现

then(onFulfilled, onRejected) {
  // 值穿透:参数不是函数时,创建默认处理函数
  onFulfilled = typeof onFulfilled === 'function' 
    ? onFulfilled 
    : value => value;
  
  onRejected = typeof onRejected === 'function' 
    ? onRejected 
    : reason => { throw reason };
  
  const promise2 = new MyPromise((resolve, reject) => {
    const fulfilledTask = () => {
      // 异步执行(微任务)
      queueMicrotask(() => {
        try {
          const x = onFulfilled(this.value);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      });
    };
    
    const rejectedTask = () => {
      queueMicrotask(() => {
        try {
          const x = onRejected(this.reason);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
          reject(e);
        }
      });
    };
    
    if (this.status === FULFILLED) {
      fulfilledTask();
    } else if (this.status === REJECTED) {
      rejectedTask();
    } else {
      // pending 状态,存储回调
      this.onFulfilledCallbacks.push(fulfilledTask);
      this.onRejectedCallbacks.push(rejectedTask);
    }
  });
  
  return promise2;
}

resolvePromise 处理返回值

这是 Promise/A+ 规范最复杂的部分:

resolvePromise(promise2, x, resolve, reject) {
  // 循环引用检测
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }
  
  // x 是 Promise
  if (x instanceof MyPromise) {
    x.then(resolve, reject);
    return;
  }
  
  // x 是对象或函数
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let called = false; // 防止多次调用
    
    try {
      const then = x.then;
      
      // then 是函数(thenable)
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            // 递归处理(y 可能还是 promise)
            this.resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        // 普通对象
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      reject(e);
    }
  } else {
    // 基本类型
    resolve(x);
  }
}

catch 和静态方法

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

static resolve(value) {
  if (value instanceof MyPromise) return value;
  return new MyPromise(resolve => resolve(value));
}

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

static all(promises) {
  return new MyPromise((resolve, reject) => {
    const results = [];
    let count = 0;
    
    promises.forEach((p, i) => {
      MyPromise.resolve(p).then(
        val => {
          results[i] = val;
          if (++count === promises.length) {
            resolve(results);
          }
        },
        reject
      );
    });
  });
}

static race(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach(p => {
      MyPromise.resolve(p).then(resolve, reject);
    });
  });
}

static allSettled(promises) {
  return new MyPromise(resolve => {
    const results = [];
    let count = 0;
    
    promises.forEach((p, i) => {
      MyPromise.resolve(p).then(
        value => {
          results[i] = { status: 'fulfilled', value };
          if (++count === promises.length) resolve(results);
        },
        reason => {
          results[i] = { status: 'rejected', reason };
          if (++count === promises.length) resolve(results);
        }
      );
    });
  });
}

四、事件循环与微任务

Promise 的回调在微任务队列执行:

console.log(1);

Promise.resolve().then(() => {
  console.log(2);
});

console.log(3);

// 输出顺序:1, 3, 2

事件循环流程

  1. 执行同步代码
  2. 执行微任务队列(Promise.then、queueMicrotask)
  3. 执行宏任务队列(setTimeout、setInterval)
  4. 更新渲染
  5. 重复
console.log('script start');

setTimeout(() => console.log('setTimeout'), 0);

Promise.resolve()
  .then(() => console.log('promise1'))
  .then(() => console.log('promise2'));

console.log('script end');

// 输出:
// script start
// script end
// promise1
// promise2
// setTimeout

五、常见陷阱与最佳实践

陷阱 1:忘记 return

// 错误:没有 return,后面的 then 拿不到结果
Promise.resolve(1)
  .then(val => {
    Promise.resolve(val + 1);
  })
  .then(val => {
    console.log(val); // undefined
  });

// 正确
Promise.resolve(1)
  .then(val => {
    return Promise.resolve(val + 1);
  })
  .then(val => {
    console.log(val); // 2
  });

陷阱 2:Promise 构造函数反模式

// 错误:不必要地包装 Promise
function getData() {
  return new Promise((resolve, reject) => {
    fetch('/api/data')
      .then(res => resolve(res.json()))
      .catch(err => reject(err));
  });
}

// 正确:直接返回
function getData() {
  return fetch('/api/data').then(res => res.json());
}

最佳实践:async/await

// Promise 链式调用
function fetchUser() {
  return getUser()
    .then(user => getOrders(user.id))
    .then(orders => getOrderDetail(orders[0].id))
    .catch(err => console.error(err));
}

// async/await 更清晰
async function fetchUser() {
  try {
    const user = await getUser();
    const orders = await getOrders(user.id);
    const detail = await getOrderDetail(orders[0].id);
    return detail;
  } catch (err) {
    console.error(err);
  }
}

并发控制

// Promise.all:全部成功才成功
const results = await Promise.all([
  fetch('/api/1'),
  fetch('/api/2'),
  fetch('/api/3'),
]);

// Promise.allSettled:不管成功失败
const results = await Promise.allSettled([
  fetch('/api/1'),
  fetch('/api/2'),
]);

// 限制并发数
async function limitConcurrency(tasks, max) {
  const results = [];
  const executing = new Set();
  
  for (const task of tasks) {
    const p = Promise.resolve().then(() => task());
    results.push(p);
    executing.add(p);
    
    const cleanup = () => executing.delete(p);
    p.then(cleanup, cleanup);
    
    if (executing.size >= max) {
      await Promise.race(executing);
    }
  }
  
  return Promise.all(results);
}

六、总结

  1. 核心:Promise 是状态机,状态只能改变一次
  2. then:返回新 Promise,支持链式调用和值穿透
  3. resolvePromise:处理返回值,支持 thenable 和循环引用检测
  4. 微任务:回调在微任务队列异步执行
  5. 实践:优先使用 async/await,注意 return 和错误处理

理解 Promise 的原理,才能写出更健壮的异步代码。

3 评论

评论区

登录 后参与评论

c
climberzbm大约 2 个月前

写的不错

管理员大约 2 个月前

感谢认可!Promise 是前端面试高频考点,希望这篇文章能帮你彻底搞懂它 💪 有问题随时交流~

管理员大约 1 个月前

感谢认可!🙌 Promise确实是JavaScript中比较难理解的概念,能帮到你我也很开心~ 有什么问题欢迎随时交流!