Skip to content

JavaScript Promise 从入门到精通:全面掌握异步编程核心

一、Promise 基础概念与核心机制

1.1 为什么需要 Promise?

在 JavaScript 中,由于其单线程特性,当执行耗时操作(如网络请求、文件读取等)时会导致页面阻塞,影响用户体验。为了解决这个问题,异步编程被提出,但传统的回调函数方式容易导致 "回调地狱",使代码难以维护。

回调地狱的问题:

javascript
doSomething(function (result) {
  doSomethingElse(
    result,
    function (newResult) {
      doThirdThing(
        newResult,
        function (finalResult) {
          console.log("Got the final result: " + finalResult);
        },
        failureCallback
      );
    },
    failureCallback
  );
}, failureCallback);

回调嵌套过深导致的问题:

  • 代码臃肿,可读性差
  • 代码复用性差
  • 耦合度高,维护困难
  • 异常处理只能在回调中进行

Promise 的出现正是为了解决这些问题,它提供了一种更优雅的异步编程解决方案,使代码更加清晰和易于维护。

1.2 Promise 基本概念与状态变化

1.2.1 Promise 的定义与基本特性

Promise 是 ES6 引入的异步编程解决方案,它是一个构造函数,自身有 all、reject、resolve 等方法,原型上有 then、catch 等方法。

简单来说,Promise 是一个容器,里面保存着某个未来才会结束的异步操作的结果。它有以下几个关键特性:

  • 状态不受外界影响:Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果才能决定当前状态,其他任何操作都无法改变。
  • 状态一旦改变就不会再变:Promise 状态的改变只有两种可能:从 pending 变为 fulfilled 或从 pending 变为 rejected。一旦状态改变,就凝固了,不会再变。这也被称为 "已定型"(resolved)。
  • 结果获取机制:如果状态改变已经发生,再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是如果你错过了它,再去监听是得不到结果的。

1.2.2 Promise 的三种状态详解

Promise 有三种状态,这三种状态构成了 Promise 的核心机制:

  • pending(进行中):初始状态,既不是成功也不是失败状态。在这个阶段,Promise 的结果还未确定。
  • fulfilled(已成功):表示异步操作成功完成,并且有了一个返回结果。此时 Promise 会调用 then 方法中定义的成功回调函数。
  • rejected(已失败):表示异步操作失败,此时 Promise 会调用 then 方法中定义的失败回调函数或 catch 方法中的回调函数。

状态转换的规则:

  • 只能从 pending 转换为 fulfilled 或 rejected
  • 一旦转换完成,状态就不能再改变
  • 转换过程不可逆

状态变化的示例:

javascript
const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 操作成功 */) {
    resolve(value); // 成功,传递结果,状态变为fulfilled
  } else {
    reject(error); // 失败,传递错误,状态变为rejected
  }
});

1.3 Promise 的基本使用方法

1.3.1 创建 Promise 对象

创建 Promise 对象的基本语法:

javascript
const promise = new Promise((resolve, reject) => {
  // 执行异步操作
  // 如果成功,调用resolve(value)
  // 如果失败,调用reject(error)
});

Promise 构造函数的执行时机: 当创建 Promise 实例时,传入的函数会立即执行。这意味着以下代码的执行顺序是确定的:

javascript
console.log("开始");

const promise = new Promise((resolve, reject) => {
  console.log("Promise构造函数执行");
  resolve();
});

console.log("结束");

promise.then(() => {
  console.log("resolved回调执行");
});

// 输出顺序:
// 开始
// Promise构造函数执行
// 结束
// resolved回调执行

这表明 Promise 构造函数内部的代码是同步执行的,而 then 方法中的回调函数是异步执行的,会在当前脚本所有同步任务执行完毕后才执行。

1.3.2 基本 API 与使用模式

Promise 提供了几个关键的实例方法和静态方法:

实例方法:

  • then(onFulfilled, onRejected):添加成功和失败的回调函数。返回一个新的 Promise 对象,可以链式调用。
  • catch(onRejected):添加失败的回调函数,相当于 then(null, onRejected)。
  • finally(onFinally):添加一个无论 Promise 成功或失败都会执行的回调函数。

静态方法:

  • Promise.resolve(value):返回一个状态为 fulfilled 的 Promise 对象。
  • Promise.reject(reason):返回一个状态为 rejected 的 Promise 对象。
  • Promise.all(iterable):接收一个 Promise 数组,返回一个新的 Promise,只有当所有 Promise 都成功时才成功;如果有任何一个 Promise 失败,新 Promise 就会失败。
  • Promise.race(iterable):接收一个 Promise 数组,返回一个新的 Promise,哪个 Promise 先完成就返回哪个 Promise 的结果。

基本使用模式:

javascript
// 创建Promise对象
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve("成功");
    } else {
      reject("失败");
    }
  }, 1000);
});

// 使用then和catch处理结果
promise
  .then((result) => {
    console.log(result); // 成功时执行
  })
  .catch((error) => {
    console.log(error); // 失败时执行
  })
  .finally(() => {
    console.log("无论成功失败都会执行"); // 无论成功失败都会执行
  });

1.4 Promise 执行顺序与事件循环

理解 Promise 的执行顺序对于掌握其工作机制至关重要。Promise 的回调函数属于微任务(Microtask),会在当前宏任务(Macrotask)执行完毕后,下一个宏任务开始前执行。

Promise 与事件循环的关系:

  • JavaScript 执行环境是单线程的,所有任务分为同步任务和异步任务。
  • 同步任务直接在调用栈中执行。
  • 异步任务分为宏任务(如 setTimeout、setInterval、I/O 操作)和微任务(如 Promise 回调、process.nextTick)。
  • 事件循环的基本流程是:执行同步任务 → 执行所有微任务 → 执行下一个宏任务。

执行顺序示例:

javascript
console.log("同步代码1");

setTimeout(() => {
  console.log("宏任务回调");
}, 0);

Promise.resolve()
  .then(() => {
    console.log("微任务回调1");
    return Promise.resolve("微任务回调2");
  })
  .then((result) => {
    console.log(result);
  });

console.log("同步代码2");

// 输出顺序:
// 同步代码1
// 同步代码2
// 微任务回调1
// 微任务回调2
// 宏任务回调

执行过程解析:

  1. 执行同步代码 console.log('同步代码 1')和 console.log('同步代码 2')。
  2. 遇到 setTimeout,将其回调函数放入宏任务队列。
  3. 遇到 Promise,立即执行其构造函数(同步执行),然后将 then 回调放入微任务队列。
  4. 当前同步代码执行完毕,检查微任务队列,执行所有微任务(两个 then 回调)。
  5. 微任务执行完毕后,执行下一个宏任务(setTimeout 回调)。

这种执行顺序确保了 Promise 的回调函数能够在当前代码块的所有同步操作完成后立即执行,同时不会阻塞后续的异步操作。

二、Promise 进阶应用与高级技巧

2.1 链式调用与错误处理

2.1.1 链式调用的原理与优势

Promise 的最大优势之一是其链式调用机制,这使得异步代码可以像同步代码一样线性书写,避免了回调地狱。

链式调用的原理: then 方法返回一个新的 Promise 对象,这使得我们可以在一个调用链中处理多个异步操作。每个 then 方法中可以返回一个值或另一个 Promise,后续的 then 方法将处理这个结果。

链式调用的基本模式:

javascript
promise
  .then((result1) => {
    // 处理result1,返回一个值或新的Promise
    return result1 + 1;
  })
  .then((result2) => {
    // 处理result2
    console.log(result2);
  })
  .catch((error) => {
    // 处理错误
    console.error(error);
  });

链式调用的优势:

  • 代码更清晰,可读性更高
  • 错误处理更统一
  • 更容易实现复杂的异步流程控制
  • 代码复用性更好

2.1.2 错误处理机制与最佳实践

在 Promise 中,错误处理是一个关键点。Promise 提供了几种错误处理方式:

基本错误处理方法:

  • 使用 catch 方法捕获错误:
javascript
promise
  .then((result) => {
    // 处理结果
  })
  .catch((error) => {
    // 处理错误
  });
  • 在 then 方法中提供第二个参数(失败回调):
javascript
promise.then(
  (result) => {
    /* 成功回调 */
  },
  (error) => {
    /* 失败回调 */
  }
);
  • 使用 finally 方法执行最终清理操作:
javascript
promise
  .then((result) => {
    /* 成功回调 */
  })
  .catch((error) => {
    /* 失败回调 */
  })
  .finally(() => {
    /* 无论成功失败都会执行 */
  });

错误冒泡机制: Promise 链中的错误会自动向上冒泡,直到被捕获。如果一个 Promise 链中没有任何 catch 方法,错误会一直向上传播,最终可能导致应用崩溃。

错误处理最佳实践:

  • 在 Promise 链的末尾添加 catch 方法,确保所有潜在错误都能被捕获。
  • 使用 try...catch 结合 async/await 来同步处理异步错误。
  • 对于可能失败的 Promise 操作,优先使用 catch 方法而不是在 then 中提供第二个参数。
  • 在 finally 中执行必要的清理操作,如关闭资源、取消订阅等。

错误处理示例:

javascript
// 错误冒泡示例
function asyncTask() {
  return new Promise((resolve, reject) => {
    throw new Error("任务失败");
  });
}

asyncTask()
  .then((result) => {
    console.log("成功:", result);
  })
  .catch((error) => {
    console.error("错误:", error); // 会捕获到错误
  });

// 自动向上冒泡示例
function step1() {
  return new Promise((resolve, reject) => {
    reject("Step1失败");
  });
}

function step2() {
  return new Promise((resolve, reject) => {
    resolve("Step2成功");
  });
}

step1()
  .then((result) => step2())
  .catch((error) => {
    console.error("捕获到错误:", error); // 会捕获到Step1的错误
  });

2.2 并发控制与任务管理

2.2.1 Promise.all 与 Promise.race 的应用

Promise 提供了两个静态方法来处理多个 Promise 的并发执行:Promise.all 和 Promise.race。

Promise.all 的应用场景: Promise.all 接收一个 Promise 数组,返回一个新的 Promise,只有当所有输入的 Promise 都成功时才会成功,返回一个包含所有结果的数组。如果有任何一个 Promise 失败,整个 Promise.all 就会立即失败。

应用场景示例:

javascript
// 同时发起多个请求并等待所有结果
const promises = [
  fetch("/api/data1"),
  fetch("/api/data2"),
  fetch("/api/data3"),
];

Promise.all(promises)
  .then((responses) => {
    // 处理所有响应
    return Promise.all(responses.map((res) => res.json()));
  })
  .then((data) => {
    console.log("所有数据:", data);
  })
  .catch((error) => {
    console.error("请求失败:", error);
  });

Promise.race 的应用场景: Promise.race 同样接收一个 Promise 数组,但只要有一个 Promise 完成(无论成功或失败),它就会立即返回该结果,其他 Promise 的结果将被忽略。

应用场景示例:

javascript
// 设置请求超时
function fetchWithTimeout(url, timeout) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error("请求超时")), timeout);
  });

  return Promise.race([fetch(url), timeoutPromise]);
}

fetchWithTimeout("/api/data", 5000)
  .then((response) => response.json())
  .then((data) => console.log("数据:", data))
  .catch((error) => console.error("错误:", error));

2.2.2 高级并发控制技巧

在实际开发中,我们常常需要更精细的并发控制,例如限制最大并发数、管理动态任务队列等。

限制最大并发数: 当有大量异步任务需要执行时,直接使用 Promise.all 可能会导致性能问题或资源耗尽。此时可以通过自定义函数来限制最大并发数。

实现思路:

  • 使用一个数组来保存当前正在执行的任务。
  • 当执行的任务数量达到限制时,等待其中一个任务完成后再继续执行下一个任务。
  • 使用 async/await 和 Promise.race 来实现阻塞和任务管理。

实现代码:

javascript
async function asyncPool(limit, tasks) {
  const results = [];
  const executing = [];

  for (const task of tasks) {
    const p = task(); // 执行任务函数,得到Promise
    results.push(p); // 将Promise存入结果数组

    // 如果并发数设置为0或大于任务数,直接执行所有任务
    if (limit <= 0 || limit >= tasks.length) continue;

    // 当正在执行的任务数达到限制时,等待其中一个完成
    if (executing.length >= limit) {
      await Promise.race(executing);
    }

    // 将当前任务加入执行队列
    executing.push(p);

    // 任务完成后从执行队列中移除
    p.then(() => executing.splice(executing.indexOf(p), 1));
  }

  return Promise.all(results);
}

// 使用示例
const tasks = [
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务1完成");
        resolve(1);
      }, 1000)
    ),
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务2完成");
        resolve(2);
      }, 2000)
    ),
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务3完成");
        resolve(3);
      }, 1500)
    ),
];

asyncPool(2, tasks).then((results) => console.log("所有任务结果:", results));

动态任务队列管理: 在某些场景下,任务不是预先确定的,而是动态生成的。这时可以使用生成器函数或队列来管理任务。

实现思路:

  • 使用一个生成器函数来动态生成任务。
  • 使用 for await...of 循环来逐个执行任务。
  • 结合并发控制逻辑,限制同时执行的任务数量。

实现代码:

javascript
async function* taskGenerator(tasks) {
  for (const task of tasks) {
    yield task();
  }
}

async function runTasks(tasks, limit) {
  const results = [];
  let pool = [];

  for await (const result of taskGenerator(tasks)) {
    pool.push(result);
    results.push(result);

    if (pool.length >= limit) {
      await Promise.race(pool);
      pool = pool.filter((p) => !p.isFulfilled && !p.isRejected);
    }
  }

  return Promise.all(results);
}

// 使用示例
const tasks = [
  () => new Promise((resolve) => setTimeout(() => resolve(1), 1000)),
  () => new Promise((resolve) => setTimeout(() => resolve(2), 2000)),
  () => new Promise((resolve) => setTimeout(() => resolve(3), 1500)),
];

runTasks(tasks, 2).then((results) => console.log("所有任务结果:", results));

2.3 超时处理与取消机制

2.3.1 实现超时处理的多种方法

在异步编程中,设置超时是一个常见需求。以下是几种实现 Promise 超时处理的方法:

方法一:使用 Promise.race 和 setTimeout 这是最基本的超时处理方法,利用 Promise.race 的特性,哪个 Promise 先完成就返回哪个结果。

javascript
function withTimeout(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => {
      setTimeout(() => reject(new Error("操作超时")), timeout);
    }),
  ]);
}

// 使用示例
const fetchData = () => fetch("/api/data");

withTimeout(fetchData(), 5000)
  .then((response) => response.json())
  .then((data) => console.log("数据:", data))
  .catch((error) => console.error("错误:", error));

方法二:使用 AbortController(现代浏览器支持) AbortController 提供了更优雅的方式来取消 Promise,特别是对于可取消的操作(如 fetch 请求)。

javascript
function fetchWithAbort(url, timeout) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  const fetchPromise = fetch(url, { signal: controller.signal }).then(
    (response) => {
      clearTimeout(timeoutId);
      return response.json();
    }
  );

  return { fetchPromise, controller };
}

// 使用示例
const { fetchPromise, controller } = fetchWithAbort("/api/data", 5000);

fetchPromise
  .then((data) => console.log("数据:", data))
  .catch((error) => {
    if (error.name === "AbortError") {
      console.error("请求已取消");
    } else {
      console.error("错误:", error);
    }
  });

// 如果需要手动取消请求,可以调用controller.abort()
// 在组件卸载时非常有用,防止内存泄漏

方法三:自定义超时处理函数 可以创建一个更通用的超时处理函数,支持更复杂的场景。

javascript
function timeout(ms) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error("操作超时"));
    }, ms);
  });
}

function withTimeout(promise, timeout) {
  return Promise.all([promise, timeout(timeout)]).then((results) => results[0]);
}

// 使用示例
withTimeout(fetchData(), 5000)
  .then((data) => console.log("数据:", data))
  .catch((error) => console.error("错误:", error));

2.3.2 取消 Promise 的高级技巧

在某些情况下,我们需要能够取消正在执行的 Promise,而不仅仅是设置超时。以下是几种实现取消的方法:

方法一:使用 AbortController(推荐) AbortController 是现代浏览器提供的 API,用于取消基于 Promise 的异步操作,特别是网络请求。

javascript
function fetchWithCancel(url) {
  const controller = new AbortController();
  const signal = controller.signal;

  const fetchPromise = fetch(url, { signal })
    .then((response) => response.json())
    .catch((error) => {
      if (error.name === "AbortError") {
        console.log("请求已取消");
      } else {
        console.error("请求失败:", error);
      }
    });

  return { fetchPromise, controller };
}

// 使用示例
const { fetchPromise, controller } = fetchWithCancel("/api/data");

// 在需要取消请求时调用
// controller.abort();

// 处理结果
fetchPromise.then((data) => console.log("数据:", data));

方法二:使用可取消的 Promise 包装器 可以创建一个通用的可取消 Promise 包装器,适用于各种异步操作。

javascript
function cancellable(promise) {
  let isCanceled = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then(
      (value) =>
        isCanceled ? reject(new Error("Promise已取消")) : resolve(value),
      (error) =>
        isCanceled ? reject(new Error("Promise已取消")) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel: () => {
      isCanceled = true;
    },
  };
}

// 使用示例
const { promise, cancel } = cancellable(fetchData());

// 在需要取消时调用cancel()
// cancel();

promise
  .then((data) => console.log("数据:", data))
  .catch((error) => console.error("错误:", error));

方法三:使用 Promise.try(ES2025 新特性) ES2025 引入了 Promise.try 方法,它提供了一种更优雅的方式来处理可能失败的同步或异步操作。

javascript
// 假设Promise.try已经可用
Promise.try(() => {
  // 可能抛出错误的同步或异步操作
})
  .then((result) => {
    // 处理成功结果
  })
  .catch((error) => {
    // 处理错误
  });

三、Promise 在主流框架中的应用

3.1 React 中的 Promise 应用

3.1.1 数据获取与状态管理

在 React 中,Promise 广泛用于数据获取和状态管理。以下是几种常见的应用场景:

使用 useEffect 获取数据:

javascript
import { useEffect, useState } from "react";

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error("用户未找到");
        const data = await response.json();
        setUser(data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]);

  if (loading) return <p>加载中...</p>;
  if (error) return <p>错误: {error}</p>;
  return <div>{/* 显示用户信息 */}</div>;
}

使用 async/await 处理多个请求:

javascript
function fetchUserData(userId) {
  return Promise.all([
    fetch(`/api/users/${userId}`),
    fetch(`/api/posts?userId=${userId}`),
  ])
    .then((responses) => Promise.all(responses.map((res) => res.json())))
    .then(([user, posts]) => ({ user, posts }));
}

// 在组件中使用
const [userData, setUserData] = useState(null);

useEffect(() => {
  fetchUserData(1)
    .then((data) => setUserData(data))
    .catch((error) => console.error(error));
}, []);

使用 React-Async 库简化异步操作: React-Async 是一个专门用于处理 React 中异步操作的库,提供了更简洁的 API。

javascript
import { useAsync } from "react-async";

function UserProfile({ userId }) {
  const { data, error, isLoading } = useAsync({
    promiseFn: () => fetch(`/api/users/${userId}`).then((res) => res.json()),
    immediate: true,
  });

  if (isLoading) return <p>加载中...</p>;
  if (error) return <p>错误: {error.message}</p>;
  return <div>{/* 显示用户信息 */}</div>;
}

3.1.2 处理组件卸载时的竞态条件

在 React 中,组件卸载时可能会有未完成的异步操作,这可能导致状态更新错误。可以通过以下方法处理:

方法一:使用 AbortController 取消请求

javascript
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`, {
          signal: controller.signal,
        });
        if (!response.ok) throw new Error("用户未找到");
        const data = await response.json();
        setUser(data);
      } catch (error) {
        if (error.name !== "AbortError") {
          setError(error.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchUser();

    return () => {
      controller.abort(); // 组件卸载时取消请求
    };
  }, [userId]);

  // 渲染逻辑
}

方法二:使用 isMounted 标志

javascript
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  let isMounted = true;

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error("用户未找到");
        const data = await response.json();
        if (isMounted) {
          setUser(data);
        }
      } catch (error) {
        if (isMounted) {
          setError(error.message);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };

    fetchUser();

    return () => {
      isMounted = false;
    };
  }, [userId]);

  // 渲染逻辑
}

3.1.3 高级模式:状态共享与并行加载

在复杂应用中,可能需要共享数据状态或并行加载多个资源。

共享数据状态: 使用 React Context 或状态管理库(如 Redux、Recoil)来共享异步数据。

javascript
// 使用Context
import { createContext, useContext, useEffect, useState } from "react";

const UserContext = createContext();

function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch("/api/user");
        if (!response.ok) throw new Error("用户未找到");
        const data = await response.json();
        setUser(data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, []);

  return (
    <UserContext.Provider value={{ user, loading, error }}>
      {children}
    </UserContext.Provider>
  );
}

function useUser() {
  return useContext(UserContext);
}

// 在子组件中使用
function Profile() {
  const { user, loading, error } = useUser();
  // 渲染逻辑
}

并行加载多个资源:

javascript
function useMultipleResources() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchResources = async () => {
      try {
        const [user, posts, comments] = await Promise.all([
          fetch("/api/user"),
          fetch("/api/posts"),
          fetch("/api/comments"),
        ]);

        const [userData, postsData, commentsData] = await Promise.all([
          user.json(),
          posts.json(),
          comments.json(),
        ]);

        setData({ user: userData, posts: postsData, comments: commentsData });
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    };

    fetchResources();
  }, []);

  return { data, loading, error };
}

// 使用示例
function Dashboard() {
  const { data, loading, error } = useMultipleResources();
  // 渲染逻辑
}

3.2 Vue 中的 Promise 应用

3.2.1 数据获取与组件生命周期

在 Vue 中,Promise 主要用于异步数据获取和组件生命周期管理。

在组件中使用 async/await 获取数据:

vue
<template>
  <div>
    <h1>{{ user.name }}</h1>
    <p v-if="loading">加载中...</p>
    <p v-if="error">{{ error }}</p>
  </div>
</template>

<script setup>
  import { ref, onMounted } from "vue";

  const user = ref(null);
  const loading = ref(true);
  const error = ref(null);

  onMounted(async () => {
    try {
      const response = await fetch("/api/user");
      if (!response.ok) throw new Error("用户未找到");
      const data = await response.json();
      user.value = data;
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  });
</script>

使用 watch 监听依赖变化并重新获取数据:

vue
<template>
  <div>
    <input v-model="userId" type="number" placeholder="用户ID" />
    <div v-if="loading">加载中...</div>
    <div v-if="user">{{ user.name }}</div>
    <div v-if="error">{{ error }}</div>
  </div>
</template>

<script setup>
  import { ref, watch } from "vue";

  const userId = ref(1);
  const user = ref(null);
  const loading = ref(false);
  const error = ref(null);

  watch(
    userId,
    async (newId) => {
      try {
        loading.value = true;
        const response = await fetch(`/api/users/${newId}`);
        if (!response.ok) throw new Error("用户未找到");
        const data = await response.json();
        user.value = data;
        error.value = null;
      } catch (err) {
        error.value = err.message;
        user.value = null;
      } finally {
        loading.value = false;
      }
    },
    { immediate: true }
  );
</script>

3.2.2 自定义指令与 Promise

在 Vue 中,可以将 Promise 与自定义指令结合,实现更复杂的功能。

自定义指令实现图片预加载:

vue
<template>
  <div v-preload-image="imageUrl">
    <img :src="imageUrl" alt="预加载图片" />
  </div>
</template>

<script setup>
  import { defineDirective } from "vue";

  const preloadImage = defineDirective((el, binding) => {
    const img = new Image();
    img.onload = () => {
      // 图片加载完成后执行的操作
    };
    img.onerror = () => {
      // 图片加载失败后执行的操作
    };
    img.src = binding.value;
  });
</script>

使用 Promise 封装自定义指令:

vue
<template>
  <div>
    <button @click="loadData">加载数据</button>
    <div v-loading="isLoading">数据内容</div>
  </div>
</template>

<script setup>
  import { ref } from "vue";

  const isLoading = ref(false);

  function loadData() {
    isLoading.value = true;
    fetch("/api/data")
      .then((response) => response.json())
      .then((data) => {
        // 处理数据
      })
      .catch((error) => {
        // 处理错误
      })
      .finally(() => {
        isLoading.value = false;
      });
  }
</script>

3.2.3 Vue 中的并发控制与错误处理

在 Vue 中,可以使用 Promise.all、Promise.race 等方法处理多个异步操作。

并行加载多个资源:

vue
<template>
  <div>
    <h1>用户信息</h1>
    <div v-if="loading">加载中...</div>
    <div v-if="error">{{ error }}</div>
    <div v-else>
      <h2>{{ user.name }}</h2>
      <p>帖子数量: {{ posts.length }}</p>
      <p>评论数量: {{ comments.length }}</p>
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted } from "vue";

  const user = ref(null);
  const posts = ref([]);
  const comments = ref([]);
  const loading = ref(true);
  const error = ref(null);

  onMounted(async () => {
    try {
      const [userRes, postsRes, commentsRes] = await Promise.all([
        fetch("/api/user"),
        fetch("/api/posts"),
        fetch("/api/comments"),
      ]);

      const [userData, postsData, commentsData] = await Promise.all([
        userRes.json(),
        postsRes.json(),
        commentsRes.json(),
      ]);

      user.value = userData;
      posts.value = postsData;
      comments.value = commentsData;
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  });
</script>

使用 Promise.allSettled 处理部分失败的情况:

vue
<template>
  <div>
    <h1>资源加载结果</h1>
    <div v-for="(result, index) in results" :key="index">
      <div v-if="result.status === 'fulfilled'">
        资源{{ index + 1 }}加载成功: {{ result.value }}
      </div>
      <div v-else>资源{{ index + 1 }}加载失败: {{ result.reason }}</div>
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted } from "vue";

  const results = ref([]);

  onMounted(async () => {
    const promises = [
      fetch("/api/resource1"),
      fetch("/api/resource2"),
      fetch("/api/resource3"),
    ];

    results.value = await Promise.allSettled(promises);
  });
</script>

3.3 Angular 中的 Promise 应用

3.3.1 使用 HttpClient 进行异步请求

在 Angular 中,HttpClient 返回的是 Observable,但可以通过 toPromise()方法将其转换为 Promise。

基本数据获取示例:

typescript
import { Component, OnInit } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Component({
  selector: "app-user-profile",
  template: `
    <div *ngIf="user">
      <h1>{{ user.name }}</h1>
      <p>Email: {{ user.email }}</p>
    </div>
    <div *ngIf="loading">加载中...</div>
    <div *ngIf="error">{{ error }}</div>
  `,
})
export class UserProfileComponent implements OnInit {
  user: any;
  loading = false;
  error: string | null = null;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.loading = true;
    this.http
      .get("/api/user")
      .toPromise()
      .then((data: any) => {
        this.user = data;
      })
      .catch((error: any) => {
        this.error = error.message;
      })
      .finally(() => {
        this.loading = false;
      });
  }
}

处理多个并行请求:

typescript
import { Component, OnInit } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Component({
  selector: "app-dashboard",
  template: `
    <div *ngIf="data">
      <h1>{{ data.user.name }}</h1>
      <p>帖子数量: {{ data.posts.length }}</p>
      <p>评论数量: {{ data.comments.length }}</p>
    </div>
    <div *ngIf="loading">加载中...</div>
    <div *ngIf="error">{{ error }}</div>
  `,
})
export class DashboardComponent implements OnInit {
  data: any;
  loading = false;
  error: string | null = null;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.loading = true;
    Promise.all([
      this.http.get("/api/user").toPromise(),
      this.http.get("/api/posts").toPromise(),
      this.http.get("/api/comments").toPromise(),
    ])
      .then(([user, posts, comments]) => {
        this.data = { user, posts, comments };
      })
      .catch((error: any) => {
        this.error = error.message;
      })
      .finally(() => {
        this.loading = false;
      });
  }
}

3.3.2 使用 async/await 与 Angular 服务

在 Angular 中,可以在服务中使用 async/await 来封装异步操作。

创建数据服务:

typescript
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Injectable({ providedIn: "root" })
export class DataService {
  constructor(private http: HttpClient) {}

  async getUser() {
    return this.http.get("/api/user").toPromise();
  }

  async getPosts() {
    return this.http.get("/api/posts").toPromise();
  }

  async getComments() {
    return this.http.get("/api/comments").toPromise();
  }
}

在组件中使用服务:

typescript
import { Component, OnInit } from "@angular/core";
import { DataService } from "./data.service";

@Component({
  selector: "app-dashboard",
  template: `
    <div *ngIf="data">
      <h1>{{ data.user.name }}</h1>
      <p>帖子数量: {{ data.posts.length }}</p>
      <p>评论数量: {{ data.comments.length }}</p>
    </div>
    <div *ngIf="loading">加载中...</div>
    <div *ngIf="error">{{ error }}</div>
  `,
})
export class DashboardComponent implements OnInit {
  data: any;
  loading = false;
  error: string | null = null;

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.loading = true;
    this.fetchData();
  }

  async fetchData() {
    try {
      const [user, posts, comments] = await Promise.all([
        this.dataService.getUser(),
        this.dataService.getPosts(),
        this.dataService.getComments(),
      ]);
      this.data = { user, posts, comments };
    } catch (error) {
      this.error = error.message;
    } finally {
      this.loading = false;
    }
  }
}

3.3.3 Angular 中的超时处理与取消机制

在 Angular 中,可以使用 AbortController 或 timeout 操作符来处理超时和取消请求。

使用 AbortController 取消请求:

typescript
import { Component, OnInit } from "@angular/core";
import { HttpClient } from "@angular/common/http";

@Component({
  selector: "app-user-profile",
  template: `
    <button (click)="fetchUser()">加载用户</button>
    <button (click)="abortRequest()">取消请求</button>
    <div *ngIf="user">用户信息: {{ user.name }}</div>
    <div *ngIf="loading">加载中...</div>
    <div *ngIf="error">{{ error }}</div>
  `,
})
export class UserProfileComponent implements OnInit {
  user: any;
  loading = false;
  error: string | null = null;
  private controller: AbortController | null = null;

  constructor(private http: HttpClient) {}

  ngOnInit() {}

  fetchUser() {
    if (this.controller) {
      this.controller.abort();
    }
    this.controller = new AbortController();
    const signal = this.controller.signal;

    this.loading = true;
    this.http
      .get("/api/user", { signal })
      .toPromise()
      .then((data: any) => {
        this.user = data;
      })
      .catch((error: any) => {
        if (error.name === "AbortError") {
          this.error = "请求已取消";
        } else {
          this.error = error.message;
        }
      })
      .finally(() => {
        this.loading = false;
      });
  }

  abortRequest() {
    if (this.controller) {
      this.controller.abort();
    }
  }
}

使用 RxJS 的 timeout 操作符:

typescript
import { Component, OnInit } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { timeout } from "rxjs/operators";

@Component({
  selector: "app-user-profile",
  template: `
    <div *ngIf="user">用户信息: {{ user.name }}</div>
    <div *ngIf="loading">加载中...</div>
    <div *ngIf="error">{{ error }}</div>
  `,
})
export class UserProfileComponent implements OnInit {
  user: any;
  loading = false;
  error: string | null = null;

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.loading = true;
    this.http
      .get("/api/user")
      .pipe(timeout(5000)) // 设置5秒超时
      .toPromise()
      .then((data: any) => {
        this.user = data;
      })
      .catch((error: any) => {
        this.error = error.message;
      })
      .finally(() => {
        this.loading = false;
      });
  }
}

四、Promise 最佳实践与性能优化

4.1 Promise 错误处理最佳实践

4.1.1 全局错误处理策略

在 JavaScript 中,可以设置全局的 Promise 拒绝处理函数,捕获所有未被处理的 Promise 错误。

Node.js 环境:

javascript
process.on("unhandledRejection", (reason, promise) => {
  console.error("未捕获的Promise拒绝:", reason);
  // 可以选择在此处处理错误或记录日志
});

浏览器环境:

javascript
window.addEventListener("unhandledrejection", (event) => {
  console.error("未捕获的Promise拒绝:", event.reason);
  event.preventDefault(); // 防止默认的控制台输出
});

使用 try...catch 包装 async 函数:

javascript
async function main() {
  try {
    await someAsyncFunction();
  } catch (error) {
    console.error("全局错误处理:", error);
  }
}

main();

4.1.2 局部错误处理策略

在 Promise 链中,错误处理应遵循以下原则:

原则一:在合适的层级捕获错误

javascript
promise
  .then((result) => {
    // 处理result
  })
  .catch((error) => {
    // 处理错误,应尽可能接近错误发生的位置
  });

原则二:避免遗漏错误处理

javascript
// 错误:没有错误处理,可能导致应用崩溃
fetchData().then((data) => processData(data));

// 正确:添加catch方法
fetchData()
  .then((data) => processData(data))
  .catch((error) => handleError(error));

原则三:在 finally 中执行清理操作

javascript
function fetchData() {
  let isLoading = true;
  return fetch("/api/data")
    .then((response) => response.json())
    .finally(() => {
      isLoading = false;
    });
}

原则四:使用 Promise.allSettled 而不是 Promise.all 处理可能失败的并行任务

javascript
// 当其中一个Promise失败时,Promise.all会立即失败
Promise.all([promise1, promise2, promise3])
  .then((results) => {
    /* 处理结果 */
  })
  .catch((error) => {
    /* 处理错误 */
  });

// 使用Promise.allSettled可以获取所有任务的结果,无论成功与否
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
  results.forEach((result) => {
    if (result.status === "fulfilled") {
      // 处理成功结果
    } else {
      // 处理失败结果
    }
  });
});

4.2 Promise 性能优化策略

4.2.1 避免 Promise 构造函数反模式

Promise 构造函数反模式是指在不需要使用 Promise 的地方滥用 Promise 构造函数,导致性能下降和代码可读性降低。

反模式示例:

javascript
// 反模式:将同步操作包装在Promise中
function add(a, b) {
  return new Promise((resolve) => {
    resolve(a + b);
  });
}

// 正确做法:直接返回结果
function add(a, b) {
  return a + b;
}

// 当确实需要异步操作时才使用Promise
function asyncAdd(a, b) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(a + b);
    }, 1000);
  });
}

避免不必要的 Promise 创建:

javascript
// 反模式:在then中创建新的Promise
promise.then((result) => {
  return new Promise((resolve) => {
    resolve(result * 2);
  });
});

// 正确做法:直接返回值
promise.then((result) => result * 2);

4.2.2 合理使用微任务与宏任务

由于 Promise 回调属于微任务,会在当前宏任务执行完毕后立即执行,因此过度使用可能会阻塞事件循环。

避免在微任务中执行大量计算:

javascript
// 反模式:在微任务中执行大量计算
Promise.resolve().then(() => {
  for (let i = 0; i < 1000000000; i++) {
    // 大量计算,阻塞事件循环
  }
});

// 正确做法:使用宏任务或Web Worker
setTimeout(() => {
  for (let i = 0; i < 1000000000; i++) {
    // 计算在宏任务中执行,不会阻塞UI
  }
}, 0);

分批处理大量任务: 当需要处理大量异步任务时,应分批处理,避免一次性启动所有任务。

javascript
function processItems(items) {
  return new Promise((resolve) => {
    let index = 0;
    const processNext = () => {
      if (index >= items.length) {
        resolve();
        return;
      }
      const item = items[index++];
      // 处理item
      // 处理完成后调用processNext,但使用setTimeout确保分批处理
      setTimeout(processNext, 0);
    };
    processNext();
  });
}

// 使用示例
processItems(largeArray).then(() => console.log("所有项目处理完成"));

4.2.3 优化 Promise 链

过长的 Promise 链可能会影响性能,应尽量优化。

合并连续的 then 调用:

javascript
// 反模式:多个then调用
promise
  .then((result) => result * 2)
  .then((result) => result + 1)
  .then((result) => result / 3);

// 正确做法:合并为一个then调用
promise.then((result) => {
  result = result * 2;
  result = result + 1;
  result = result / 3;
  return result;
});

避免不必要的链式调用:

javascript
// 反模式:不必要的链式调用
const p1 = Promise.resolve(1);
const p2 = p1.then((result) => result * 2);
const p3 = p2.then((result) => result + 1);
const p4 = p3.then((result) => result / 3);

// 正确做法:直接在一个链中处理
const p = Promise.resolve(1)
  .then((result) => result * 2)
  .then((result) => result + 1)
  .then((result) => result / 3);

使用 async/await 优化代码结构:

javascript
// 使用Promise链
fetchData()
  .then((data) => processData(data))
  .then((result) => saveResult(result))
  .catch((error) => handleError(error));

// 使用async/await
async function processData() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    await saveResult(result);
  } catch (error) {
    handleError(error);
  }
}

4.3 与 async/await 结合的最佳实践

4.3.1 async/await 基础与优势

async/await 是 ES2017 引入的异步编程语法糖,基于 Promise 实现,提供了更简洁、直观的异步代码编写方式。

基本语法:

javascript
async function asyncFunction() {
  try {
    const result = await promise;
    // 处理结果
  } catch (error) {
    // 处理错误
  }
}

async/await 的优势:

  • 代码结构更接近同步代码,可读性更高
  • 错误处理更简单,使用传统的 try...catch
  • 避免了 Promise 链的嵌套
  • 更容易实现复杂的异步流程控制

4.3.2 错误处理与资源清理

在使用 async/await 时,错误处理和资源清理是关键点。

错误处理最佳实践:

javascript
// 使用try...catch捕获错误
async function fetchData() {
  try {
    const response = await fetch("/api/data");
    if (!response.ok) throw new Error("请求失败");
    const data = await response.json();
    return data;
  } catch (error) {
    console.error("错误:", error);
    throw error; // 可以选择重新抛出错误
  }
}

// 使用try...catch处理多个await
async function processData() {
  try {
    const data1 = await fetchData1();
    const data2 = await fetchData2();
    // 处理数据
  } catch (error) {
    // 处理错误
  }
}

资源清理:

javascript
async function fetchDataWithAbort() {
  const controller = new AbortController();
  const signal = controller.signal;

  try {
    const response = await fetch("/api/data", { signal });
    const data = await response.json();
    return data;
  } catch (error) {
    if (error.name === "AbortError") {
      console.log("请求已取消");
    } else {
      console.error("错误:", error);
    }
  } finally {
    // 无论成功与否,都可以在这里执行清理操作
    // 例如关闭文件、取消订阅等
  }
}

4.3.3 处理多个异步操作

async/await 与 Promise.all 结合可以更方便地处理多个异步操作。

并行执行多个异步操作:

javascript
async function loadData() {
  const [user, posts, comments] = await Promise.all([
    fetch("/api/user"),
    fetch("/api/posts"),
    fetch("/api/comments"),
  ]);

  const [userData, postsData, commentsData] = await Promise.all([
    user.json(),
    posts.json(),
    comments.json(),
  ]);

  return { user: userData, posts: postsData, comments: commentsData };
}

顺序执行多个异步操作:

javascript
async function sequentialLoad() {
  const user = await fetch("/api/user").then((res) => res.json());
  const posts = await fetch(`/api/posts?userId=${user.id}`).then((res) =>
    res.json()
  );
  const comments = await fetch(`/api/comments?postId=${posts[0].id}`).then(
    (res) => res.json()
  );
  return { user, posts, comments };
}

条件执行异步操作:

javascript
async function conditionalLoad() {
  const user = await fetch("/api/user").then((res) => res.json());
  let posts;
  if (user.isAdmin) {
    posts = await fetch("/api/admin/posts").then((res) => res.json());
  } else {
    posts = await fetch("/api/public/posts").then((res) => res.json());
  }
  return { user, posts };
}

五、高级主题与未来趋势

5.1 自定义 Promise 实现

5.1.1 Promise/A + 规范与实现原理

Promise/A + 是一个社区规范,定义了 Promise 的行为和接口。理解其规范有助于更好地掌握 Promise 的工作原理。

Promise/A + 规范关键点:

  • Promise 有三种状态:pending、fulfilled、rejected
  • 状态只能由 pending 转换为 fulfilled 或 rejected,且只能转换一次
  • then 方法接受两个回调函数,分别处理成功和失败情况
  • then 方法返回一个新的 Promise,可以实现链式调用

简单的 Promise 实现:

javascript
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 (error) {
      reject(error);
    }
  }

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

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === "fulfilled") {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === "rejected") {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === "pending") {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });

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

    return promise2;
  }

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

  static resolve(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 result = [];
      let count = 0;

      const processData = (index, data) => {
        result[index] = data;
        if (++count === promises.length) {
          resolve(result);
        }
      };

      for (let i = 0; i < promises.length; i++) {
        promises[i].then((data) => processData(i, data), reject);
      }
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(resolve, reject);
      }
    });
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise"));
  }

  let called = false;

  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    try {
      const then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (err) => {
            if (called) return;
            called = true;
            reject(err);
          }
        );
      } else {
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}

5.1.2 扩展 Promise 功能

可以通过继承和原型扩展来增强 Promise 的功能。

添加超时功能:

javascript
class TimeoutPromise extends MyPromise {
  constructor(executor, timeout) {
    super(executor);
    this.timeout = timeout;
    this.timer = null;

    this._initTimeout();
  }

  _initTimeout() {
    this.timer = setTimeout(() => {
      this.reject(new Error("操作超时"));
    }, this.timeout);
  }

  then(onFulfilled, onRejected) {
    const promise = super.then(onFulfilled, onRejected);
    promise.finally(() => {
      clearTimeout(this.timer);
    });
    return promise;
  }

  // 其他方法...
}

// 使用示例
const promise = new TimeoutPromise((resolve) => {
  setTimeout(resolve, 2000, "成功");
}, 3000);

promise
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

添加取消功能:

javascript
class CancelablePromise extends MyPromise {
  constructor(executor) {
    super(executor);
    this.isCanceled = false;
  }

  cancel() {
    this.isCanceled = true;
    // 可以在这里执行取消操作
  }

  then(onFulfilled, onRejected) {
    const wrappedOnFulfilled = (value) => {
      return this.isCanceled
        ? Promise.reject(new Error("Promise已取消"))
        : onFulfilled(value);
    };

    const wrappedOnRejected = (reason) => {
      return this.isCanceled
        ? Promise.reject(new Error("Promise已取消"))
        : onRejected(reason);
    };

    return super.then(wrappedOnFulfilled, wrappedOnRejected);
  }

  // 其他方法...
}

// 使用示例
const promise = new CancelablePromise((resolve) => {
  setTimeout(() => {
    if (!promise.isCanceled) {
      resolve("成功");
    }
  }, 2000);
});

// 在需要取消时调用
// promise.cancel();

promise
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

5.2 高级并发控制模式

5.2.1 任务优先级管理

在实际应用中,不同的异步任务可能有不同的优先级,需要优先处理高优先级任务。

基于优先级的任务执行器:

javascript
class PriorityTaskQueue {
  constructor(maxConcurrency) {
    this.maxConcurrency = maxConcurrency;
    this.tasks = [];
    this.running = [];
  }

  addTask(task, priority) {
    this.tasks.push({ task, priority });
    this.tasks.sort((a, b) => b.priority - a.priority); // 按优先级降序排序
    this.processNext();
  }

  processNext() {
    while (this.running.length < this.maxConcurrency && this.tasks.length > 0) {
      const { task } = this.tasks.shift();
      const promise = task();
      this.running.push(promise);
      promise.finally(() => {
        this.running.splice(this.running.indexOf(promise), 1);
        this.processNext();
      });
    }
  }
}

// 使用示例
const queue = new PriorityTaskQueue(2);

queue.addTask(
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务1完成");
        resolve(1);
      }, 2000)
    ),
  2
);
queue.addTask(
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务2完成");
        resolve(2);
      }, 1000)
    ),
  3
);
queue.addTask(
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务3完成");
        resolve(3);
      }, 1500)
    ),
  1
);

5.2.2 动态任务调度

动态任务调度允许在运行时动态添加任务,并根据当前状态调整执行策略。

动态任务调度器:

javascript
class DynamicTaskScheduler {
  constructor(maxConcurrency) {
    this.maxConcurrency = maxConcurrency;
    this.queue = [];
    this.running = [];
  }

  addTask(task) {
    this.queue.push(task);
    this.processNext();
  }

  processNext() {
    while (this.running.length < this.maxConcurrency && this.queue.length > 0) {
      const task = this.queue.shift();
      const promise = task();
      this.running.push(promise);
      promise.finally(() => {
        this.running.splice(this.running.indexOf(promise), 1);
        this.processNext();
      });
    }
  }

  // 可以添加暂停、恢复、取消等方法
}

// 使用示例
const scheduler = new DynamicTaskScheduler(2);

// 动态添加任务
scheduler.addTask(
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务1完成");
        resolve(1);
      }, 2000)
    )
);
scheduler.addTask(
  () =>
    new Promise((resolve) =>
      setTimeout(() => {
        console.log("任务2完成");
        resolve(2);
      }, 1000)
    )
);
setTimeout(() => {
  scheduler.addTask(
    () =>
      new Promise((resolve) =>
        setTimeout(() => {
          console.log("任务3完成");
          resolve(3);
        }, 1500)
      )
  );
}, 1500);

5.2.3 重试机制与指数退避

在处理可能失败的异步操作时,重试机制和指数退避可以提高系统的稳定性。

带重试和指数退避的 Promise 封装:

javascript
function retryWithBackoff(fn, maxRetries, initialDelay) {
  return new Promise(async (resolve, reject) => {
    let retries = 0;
    let delay = initialDelay || 1000;

    const attempt = async () => {
      try {
        const result = await fn();
        resolve(result);
      } catch (error) {
        retries++;
        if (retries > maxRetries) {
          reject(error);
          return;
        }
        console.log(`重试 ${retries} 次,等待 ${delay} 毫秒`);
        await new Promise((resolve) => setTimeout(resolve, delay));
        delay *= 2; // 指数退避
        await attempt();
      }
    };

    await attempt();
  });
}

// 使用示例
const fetchWithRetry = retryWithBackoff(
  () => fetch("/api/data").then((res) => res.json()),
  3,
  1000
);

fetchWithRetry
  .then((data) => console.log("数据:", data))
  .catch((error) => console.error("最终失败:", error));

5.3 ES2025 中的 Promise 新特性

5.3.1 Promise.try 的引入与应用

ES2025 引入了 Promise.try 方法,它提供了一种更简洁的方式来处理可能失败的同步或异步操作。

Promise.try 的基本用法:

javascript
Promise.try(() => {
  // 可能抛出错误的同步或异步操作
  return someFunction();
})
  .then((result) => {
    // 处理成功结果
  })
  .catch((error) => {
    // 处理错误
  });

与传统方法的对比:

javascript
// 传统方法
try {
  const result = syncFunction();
  return Promise.resolve(result);
} catch (error) {
  return Promise.reject(error);
}

// 使用Promise.try
Promise.try(() => syncFunction());

优势:

  • 简化了同步和异步操作的统一处理
  • 减少了样板代码
  • 更清晰地表达意图

5.3.2 性能优化与自动取消机制

ES2025 中的 Promise 新特性还包括性能优化和自动取消机制。

性能提升: Promise.try 对于同步操作不会推入微任务队列,直接执行,这使得同步操作的性能提升约 10 倍。

自动取消机制: 未来的 Promise 可能会提供更原生的取消机制,类似于 AbortController,但更集成到 Promise API 中。

javascript
// 假设的未来API
const promise = new Promise((resolve, reject) => {
  // 异步操作
  const cancel = () => {
    // 取消操作
    reject(new Error("操作已取消"));
  };
  // 返回取消函数
  return cancel;
});

// 取消Promise
const cancel = promise.cancel();
cancel();

5.3.3 与其他 ES 特性的结合

未来的 Promise 可能会与其他 ES 特性更紧密地结合,如 Top-level await、模块加载等。

Top-level await: 允许在模块顶层使用 await,这使得模块可以异步加载。

javascript
// 模块A
export const data = await fetch("/api/data").then((res) => res.json());

// 模块B
import { data } from "./moduleA.js";
console.log(data);

与信号(Signals)的结合: 信号(Signals)是一种新的响应式编程模型,可能与 Promise 结合,提供更强大的异步状态管理。

javascript
import { signal } from "signals";

const data = signal(null);
const error = signal(null);
const loading = signal(true);

Promise.try(() => fetch("/api/data"))
  .then((response) => response.json())
  .then((result) => (data.value = result))
  .catch((err) => (error.value = err))
  .finally(() => (loading.value = false));

六、总结与实践建议

6.1 Promise 核心知识点回顾

在本文中,我们全面探讨了 JavaScript Promise 的核心概念、高级应用以及在主流框架中的使用。以下是关键知识点回顾:

基础概念:

  • Promise 是 ES6 引入的异步编程解决方案,用于替代回调函数,解决回调地狱问题。
  • Promise 有三种状态:pending、fulfilled、rejected,状态一旦改变就不会再变。
  • Promise 构造函数接收一个执行器函数,包含 resolve 和 reject 两个参数。
  • Promise 通过 then、catch、finally 方法处理结果和错误。

核心机制:

  • Promise 的回调函数属于微任务,会在当前宏任务执行完毕后立即执行。
  • 链式调用是 Promise 的核心特性,每个 then 方法返回一个新的 Promise。
  • 错误会在 Promise 链中自动冒泡,直到被 catch 方法捕获。

高级应用:

  • Promise.all 用于并行执行多个 Promise,所有都成功才成功。
  • Promise.race 用于并行执行多个 Promise,哪个先完成就返回哪个结果。
  • 并发控制可以通过自定义函数或第三方库(如 p-limit)实现。
  • 超时处理可以使用 Promise.race 结合 setTimeout 或 AbortController。

框架集成:

  • 在 React 中,Promise 用于数据获取、状态管理和组件卸载时的竞态条件处理。
  • 在 Vue 中,Promise 与生命周期钩子和自定义指令结合,实现异步数据加载。
  • 在 Angular 中,HttpClient 返回的 Observable 可以通过 toPromise()转换为 Promise。

最佳实践:

  • 使用 try...catch 结合 async/await 处理错误。
  • 在 finally 中执行清理操作,确保资源正确释放。
  • 避免 Promise 构造函数反模式,不必要地包装同步操作。
  • 合理使用微任务和宏任务,避免阻塞事件循环。

6.2 不同场景下的选择策略

根据不同的应用场景,应选择合适的异步编程方法:

简单异步操作:

  • 当只需要处理一个简单的异步操作时,直接使用 Promise 即可。
  • 推荐使用 async/await 语法,提高代码可读性。

复杂异步流程:

  • 当需要处理多个异步操作,且存在依赖关系时,使用 Promise 链或 async/await。
  • 对于并行操作,使用 Promise.all 或 Promise.allSettled。
  • 对于需要优先级管理或动态调度的任务,考虑自定义并发控制机制。

框架特定场景:

  • React:使用 useEffect 和 async/await 处理数据获取,使用 AbortController 取消请求。
  • Vue:在 onMounted 钩子中使用 async/await,结合 watch 处理依赖变化。
  • Angular:在服务中封装异步操作,使用 HttpClient 和 toPromise()方法。

性能敏感场景:

  • 避免在微任务中执行大量计算,使用宏任务或 Web Worker。
  • 限制最大并发数,避免同时发起过多请求。
  • 使用 Promise.try 优化同步操作的性能。

6.3 未来学习路径与资源推荐

要进一步深入掌握 Promise 和异步编程,可以从以下几个方向继续学习:

深入学习路径:

  • Promise/A + 规范:阅读官方规范,理解 Promise 的底层实现原理。
  • 自定义 Promise 实现:尝试实现一个符合 Promise/A + 规范的 Promise,加深理解。
  • 异步框架源码分析:学习 React、Vue 等框架的异步机制实现。
  • 并发控制算法:研究更复杂的并发控制算法和策略。
  • ES 新特性追踪:关注 ES2025 及后续版本中与异步编程相关的新特性。

推荐资源:

  • MDN 文档:Promise
  • Promise/A + 规范:Promises/A+
  • 书籍:《JavaScript 高级程序设计》(第 4 版)中的异步编程章节

优秀文章:

  • JavaScript Promise 迷你书
  • 深入理解 JavaScript Promise

框架文档:

  • React 中的异步操作
  • Vue 中的异步操作
  • Angular 中的 HTTP 请求

实践建议:

  • 小型项目实践:创建一个简单的应用,使用 Promise 处理所有异步操作。
  • 错误处理练习:编写代码模拟各种错误情况,练习全面的错误处理。
  • 并发控制实现:尝试实现一个自定义的并发控制函数,支持最大并发数限制。
  • 框架集成实践:在 React、Vue 或 Angular 项目中使用 Promise 完成复杂的数据加载流程。
  • 性能优化挑战:寻找现有代码中的 Promise 性能瓶颈,尝试优化。

通过系统学习和实践,你将能够全面掌握 JavaScript Promise 的核心概念和高级应用,为成为前端架构师奠定坚实的基础。Promise 作为现代异步编程的核心工具,不仅在日常开发中广泛应用,也是理解和使用各种前端框架的关键。

Released under the MIT License.