# Promise 那些事儿

# Promise 的历史由来

Promise概念最早由Daniel P. Friedman和David Wise在1976年提出,并在1977年与Gary H. MacAlistair共同发表的论文《The Impact of Applicative Programming on Multiprocessing》中详细阐述。

在JavaScript中的发展历程:

  1. 早期实现

    • 2007年:Dojo框架首次在JavaScript中实现Promise模式
    • 2009年:CommonJS项目制定了Promises/A规范
  2. 标准化进程

    • 2012年:Promises/A+规范发布,成为事实标准
    • 2015年:ES6(ES2015)正式将Promise纳入语言标准
  3. 现代发展

    • 2017年:Async/Await语法糖基于Promise实现
    • 2021年:Promise.any等新方法加入ES2021

Promise解决问题:

  • 回调地狱(Callback Hell)
  • 异步流程控制困难
  • 错误处理复杂
  • 代码可读性差

# JavaScript 中的 Promise

const promise = new Promise((hello, world) => {
  console.log("init");
  setTimeout(() => {
    console.log("------------->pending");
    hello(); // 状态改变,仅此一次
    console.log("fulfilled");
    world();
    return;
    console.log("after return 不会执行");
  }, 0);
  console.log("pending------------->");
});
console.log("同步");
promise
  .then(() => {
    console.log("then");
  })
  .catch(() => {
    console.log("catch");
  });
// init
// 同步
// fulfilled
// then

# Promise 的 A+规范

Promise A+规范核心要点:

# 状态机

Promise的状态机(State Machine)是Promise实现异步控制的核心机制,它具有以下特点:

  1. 三种确定状态

    • Pending(等待态):初始状态,既没有被兑现也没有被拒绝
    • Fulfilled(已兑现):操作成功完成
    • Rejected(已拒绝):操作失败
  2. 状态转换规则

    • 状态只能从Pending变为Fulfilled或Rejected
    • 状态一旦改变就不可逆转(不可再变回Pending或其他状态)
    • 状态改变是原子性的(要么成功要么失败)
  3. 状态与值的关系

    • Fulfilled状态必须有一个值(value)
    • Rejected状态必须有一个原因(reason)
enum PromiseState {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected'
}

class MyPromise<T> {
  private state: PromiseState = PromiseState.PENDING;
  private value: T | undefined;
  private reason: any;
  // ... other implementations
}

状态机的重要性:

  • 保证异步操作的确定性
  • 提供清晰的执行流程控制
  • 防止竞态条件(Race Condition)
  • 为链式调用提供基础

# then 方法

then方法是Promise的核心方法,它定义了Promise的异步处理流程。根据A+规范,then方法具有以下特性:

  1. 方法签名

    • 接收两个可选参数:onFulfilled和onRejected回调函数
    • 返回一个新的Promise对象,支持链式调用
  2. 参数处理规则

    • 如果onFulfilled不是函数,则忽略并向下传递值
    • 如果onRejected不是函数,则忽略并向下传递错误
  3. 执行时机

    • 当Promise状态变为fulfilled时调用onFulfilled
    • 当Promise状态变为rejected时调用onRejected
    • 回调函数总是异步执行(微任务队列)
interface Thenable<T> {
  then<U>(
    onFulfilled?: (value: T) => U | Thenable<U>,
    onRejected?: (reason: any) => U | Thenable<U>
  ): Promise<U>;
}

class MyPromise<T> implements Thenable<T> {
  then<U>(
    onFulfilled?: (value: T) => U | Thenable<U>,
    onRejected?: (reason: any) => U | Thenable<U>
  ): Promise<U> {
    // ... implementation
  }
}

then方法的重要性:

  • 实现Promise链式调用的基础
  • 提供统一的异步处理接口
  • 支持值穿透和错误冒泡
  • 保证回调函数的执行顺序

# 值穿透

值穿透(Value Penetration)是Promise A+规范中的重要特性,指当then方法的回调函数不是函数类型时,Promise会将值"穿透"到链式调用的下一个then中。

const p = new Promise((resolve) => resolve(42));

// 值穿透示例
p.then()
 .then()
 .then((value) => {
   console.log(value); // 42
 });

值穿透的实现原理:

  1. 规范要求then方法的参数必须是函数,如果不是函数则创建默认函数
  2. 默认的onFulfilled函数会将接收到的值原样返回
  3. 默认的onRejected函数会将错误原因原样抛出 这种行为保证了Promise链式调用的连续性,即使中间有空的then()调用也不会中断值的传递。

# 链式调用

Promise链式调用(Chaining)是Promise的核心特性之一,它允许我们将多个异步操作按顺序连接起来。链式调用的关键点在于:

  1. then方法返回新Promise

    • 每个then()方法都会返回一个新的Promise对象
    • 这使得我们可以连续调用多个then()方法
  2. 值传递机制

    • 前一个then()回调的返回值会作为参数传递给下一个then()
    • 如果返回的是Promise,会等待该Promise解决后再继续
  3. 错误冒泡

    • 链式调用中的错误会一直向下传递,直到被catch()捕获
    • 这使得错误处理更加集中和方便
function fetchData(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => resolve('data'), 1000);
  });
}

fetchData()
  .then((data) => {
    console.log(data);
    return data.toUpperCase();
  })
  .then((upperData) => {
    console.log(upperData);
  });

链式调用的优势:

  • 避免回调地狱(Callback Hell)
  • 使异步代码具有同步代码的可读性
  • 便于错误处理和流程控制

# Promise 的应用

# Promise 串行执行

  1. 基本串行执行 - 使用async/await顺序执行
async function serialExecution(): Promise<void> {
  const tasks = [
    () => new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    () => new Promise(resolve => setTimeout(() => resolve(2), 500)),
    () => new Promise(resolve => setTimeout(() => resolve(3), 300))
  ];

  for (const task of tasks) {
    const result = await task();
    console.log(result); // 依次输出: 1 (1秒后), 2 (0.5秒后), 3 (0.3秒后)
  }
}
  1. 带错误处理的串行执行
async function serialWithErrorHandling(): Promise<void> {
  const tasks = [
    () => new Promise(resolve => resolve(1)),
    () => new Promise((_, reject) => reject('error')),
    () => new Promise(resolve => resolve(3))
  ];

  const results: Array<number | string> = [];
  for (const task of tasks) {
    try {
      results.push(await task());
    } catch (error) {
      results.push(error as string);
    }
  }
  console.log(results); // [1, "error", 3]
}
  1. 带超时处理的串行执行
async function serialWithTimeout(): Promise<void> {
  const tasks = [
    () => new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    () => new Promise(resolve => setTimeout(() => resolve(2), 500)),
    () => new Promise(resolve => setTimeout(() => resolve(3), 300))
  ];

  for (const task of tasks) {
    try {
      // 为每个任务设置超时
      const result = await Promise.race([
        task(),
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('Timeout')), 800)
        )
      ]);
      console.log(result);
    } catch (error) {
      console.error('任务超时:', error.message);
    }
  }
}

// 执行结果:
// 1 (正常完成)
// Error: 任务超时: Timeout (第二个任务超时)
// 3 (正常完成)

# Promise 并发执行

async function concurrentAll(): Promise<void> {
  const promises = [
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3)
  ];
  
  try {
    const results = await Promise.all(promises);
    console.log(results); // [1, 2, 3]
  } catch (error) {
    console.error(error);
  }
}

# 并发控制

p-limit 是一个用于控制 Promise 并发执行数量的实用库,特别适合需要限制并发请求数量的场景

import pLimit from 'p-limit';

// 创建一个并发限制为2的limit函数
const limit = pLimit(2);

const tasks = [
  limit(() => fetchSomething('foo')),
  limit(() => fetchSomething('bar')),
  limit(() => fetchSomething('baz'))
];

// 执行所有任务
const result = await Promise.all(tasks);
console.log(result); // [result1, result2, result3]

使用场景:

  1. API请求限流
  2. 文件批量处理
  3. 数据库操作并发控制
  4. 爬虫任务调度

# 学习资料

Promise 的前世今生 (opens new window)

Futures_and_promises - Wikipedia (opens new window)

Future 与 promise - 维基百科 (opens new window)

JavaScript 中的异步冒险:Promises (opens new window)

你应该学习的 JS 异步编程与Promise (opens new window)

手写PROMISE核心代码,提升JAVASCRIPT编程能力 (opens new window)