Promise

76 阅读5分钟

一、Promise 的出现背景

1. 回调地狱问题

在 Promise 出现之前,JavaScript 异步编程主要依赖回调函数,导致:

  • 嵌套层级深:多个异步操作形成"金字塔"结构
  • 错误处理困难:需要在每个回调中单独处理错误
  • 流程控制复杂:难以实现复杂的异步逻辑组合
// 典型的回调地狱
getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      getMoreData(c, function(d) {
        console.log(d);
      }, failureCallback);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

2. Promise 的解决方案

Promise 提供了:

  • 链式调用:通过 .then() 方法扁平化异步流程
  • 统一的错误处理:通过 .catch() 集中处理错误
  • 状态不可逆:确保异步操作结果的一致性

二、Promise 核心概念

1. 三种状态

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

image.png

2. 基本用法

const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
});

promise
  .then(value => { /* 成功处理 */ })
  .catch(error => { /* 失败处理 */ });

三、Promise API 详解

1. 构造函数

new Promise(executor);
  • executor:执行函数,接收 resolvereject 两个参数
  • 立即执行,在构造函数中抛出错误会导致 Promise 拒绝

2. 实例方法

then()

promise.then(
  onFulfilled?: (value: any) => any,
  onRejected?: (error: any) => any
) => Promise
  • 返回新 Promise,支持链式调用
  • 参数可选,省略时值会透传

catch()

promise.catch(
  onRejected?: (error: any) => any
) => Promise
  • 相当于 .then(null, onRejected)
  • 捕获链上所有错误

finally()

promise.finally(
  onFinally?: () => void
) => Promise
  • 无论成功失败都会执行
  • 不接收参数,不影响最终值

3. 静态方法

Promise.resolve()

Promise.resolve(value) => Promise
  • 创建已解决的 Promise
  • 如果参数是 Promise,则直接返回

Promise.reject()

Promise.reject(reason) => Promise
  • 创建已拒绝的 Promise

Promise.all()

Promise.all(iterable) => Promise
  • 所有 Promise 成功时返回结果数组
  • 任何一个失败立即拒绝

Promise.allSettled()

Promise.allSettled(iterable) => Promise
  • 等待所有 Promise 完成(无论成功失败)
  • 返回包含状态和结果的对象数组

Promise.race()

Promise.race(iterable) => Promise
  • 取最先完成的 Promise 结果(无论成功失败)

Promise.any()

Promise.any(iterable) => Promise
  • 取最先成功的 Promise 结果
  • 全部失败时返回 AggregateError

四、Promise 高级特性

1. 值透传

.then 缺少处理函数时,值会直接传递到下一个 .then

Promise.resolve(1)
  .then(2)
  .then()
  .then(console.log); // 输出 1

2. 错误冒泡

错误会沿着链一直传递,直到被 .catch 捕获:

Promise.reject(new Error('fail'))
  .then(val => console.log(val))
  .then(val => console.log(val))
  .catch(err => console.error(err)); // 捕获错误

3. 返回新 Promise

.then 中可以返回新 Promise,后续处理会等待其完成:

Promise.resolve()
  .then(() => new Promise(resolve => 
    setTimeout(() => resolve('done'), 1000)
  ))
  .then(console.log); // 1秒后输出 "done"

4. 同步抛出与异步拒绝

  • 构造函数中同步抛出错误会导致 Promise 拒绝
  • .then 中同步抛出会转换为拒绝的 Promise
new Promise(() => { throw new Error('sync error'); })
  .catch(console.error); // 捕获同步错误

Promise.resolve()
  .then(() => { throw new Error('async error'); })
  .catch(console.error); // 捕获异步错误

五、Promise 使用模式

1. 顺序执行

function executeSequentially(promises) {
  return promises.reduce((chain, promise) => {
    return chain.then(() => promise);
  }, Promise.resolve());
}

2. 超时控制

function timeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), ms)
    )
  ]);
}

3. 重试机制

function retry(fn, times, delay) {
  return new Promise((resolve, reject) => {
    const attempt = (n) => {
      fn().then(resolve)
        .catch(err => {
          if (n === 0) return reject(err);
          setTimeout(() => attempt(n - 1), delay);
        });
    };
    attempt(times);
  });
}

4. 并发控制

async function parallelWithLimit(tasks, limit) {
  const results = [];
  const executing = [];
  
  for (const task of tasks) {
    const p = task().then(r => {
      executing.splice(executing.indexOf(p), 1);
      return r;
    });
    
    results.push(p);
    executing.push(p);
    
    if (executing.length >= limit) {
      await Promise.race(executing);
    }
  }
  
  return Promise.all(results);
}

六、Promise 与 async/await

1. 基本转换

// Promise 风格
function fetchData() {
  return fetch(url)
    .then(response => response.json())
    .catch(console.error);
}

// async/await 风格
async function fetchData() {
  try {
    const response = await fetch(url);
    return response.json();
  } catch (err) {
    console.error(err);
  }
}

2. 注意事项

  • await 会暂停函数执行,直到 Promise 完成
  • async 函数总是返回 Promise
  • 并行操作应使用 Promise.all
// 顺序执行(慢)
async function sequential() {
  const a = await task1();
  const b = await task2();
  return a + b;
}

// 并行执行(快)
async function parallel() {
  const [a, b] = await Promise.all([task1(), task2()]);
  return a + b;
}

七、Promise 实现原理

1. 简易实现

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });

    return promise2;
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 实现解析过程
}

2. Promises/A+ 规范要点

  1. then 方法必须返回 Promise
  2. 值穿透:如果 onFulfilled/onRejected 不是函数必须忽略
  3. 异步执行then 的回调必须异步执行
  4. 递归解析:处理 thenable 对象

八、最佳实践与常见错误

1. 最佳实践

  • 总是返回 Promise:确保链式调用不中断
  • 错误处理:每个 Promise 链都应有 .catch
  • 命名 Promise:调试时更有意义
  • 避免冗余嵌套:扁平化 Promise 链

2. 常见错误

错误1:忘记 return

// 错误:第二个 then 接收 undefined
Promise.resolve()
  .then(() => { doSomething(); })
  .then(result => console.log(result));

// 正确
Promise.resolve()
  .then(() => doSomething())
  .then(result => console.log(result));

错误2:忽略错误处理

// 错误:未处理的拒绝
function fetchData() {
  return fetch(url).then(r => r.json());
}

// 正确
function fetchData() {
  return fetch(url)
    .then(r => r.json())
    .catch(err => {
      console.error(err);
      throw err; // 继续传递错误
    });
}

错误3:过度嵌套

// 错误:不必要的嵌套
function getData() {
  return fetch(url1).then(r1 => {
    return fetch(url2).then(r2 => {
      return fetch(url3).then(r3 => {
        return [r1, r2, r3];
      });
    });
  });
}

// 正确:扁平化
function getData() {
  return fetch(url1)
    .then(r1 => fetch(url2).then(r2 => [r1, r2]))
    .then(([r1, r2]) => fetch(url3).then(r3 => [r1, r2, r3]));
}

// 更好:使用 async/await
async function getData() {
  const r1 = await fetch(url1);
  const r2 = await fetch(url2);
  const r3 = await fetch(url3);
  return [r1, r2, r3];
}

九、浏览器兼容性与 Polyfill

1. 兼容性

  • 现代浏览器全面支持
  • IE11 及以下不支持,需要 polyfill

2. 推荐 Polyfill

3. 使用方式

<!-- 使用 es6-promise -->
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4.2.8/dist/es6-promise.auto.min.js"></script>
<script>
  // 现在可以使用 Promise
</script>

十、总结

Promise 是 JavaScript 异步编程的基础,提供了:

  1. 更清晰的异步代码结构:通过链式调用替代回调嵌套
  2. 更好的错误处理:集中捕获异步错误
  3. 强大的组合能力:通过 Promise.all/race 等组合异步操作
  4. async/await 的基础:使异步代码看起来像同步代码