Skip to content

异步编程手写题

掌握 Promise 相关方法和异步并发控制

1. Promise.all / race / allSettled

难度: ⭐⭐⭐ | 梯队: 第一梯队 ✅ | 标签: 异步, Promise

题目描述

实现 Promise.allPromise.racePromise.allSettled 三个静态方法。

  • Promise.all: 所有 Promise 都成功才 resolve,任一失败立即 reject
  • Promise.race: 任意一个 Promise 完成(成功或失败)就返回
  • Promise.allSettled: 等待所有 Promise 完成,无论成功失败

代码实现

javascript
/**
 * Promise.all 实现
 * 所有成功才 resolve,任一失败就 reject
 * @param {Iterable} promises
 * @return {Promise}
 */
Promise.myAll = function (promises) {
  return new Promise((resolve, reject) => {
    // 处理非数组输入(可迭代对象)
    const promiseArray = Array.from(promises);
    const results = new Array(promiseArray.length);
    let resolvedCount = 0;

    // 空数组直接返回
    if (promiseArray.length === 0) {
      resolve(results);
      return;
    }

    promiseArray.forEach((promise, index) => {
      // 使用 Promise.resolve 包装,处理非 Promise 值
      Promise.resolve(promise)
        .then((value) => {
          results[index] = value;  // 保持顺序
          resolvedCount++;

          if (resolvedCount === promiseArray.length) {
            resolve(results);
          }
        })
        .catch(reject);  // 任一失败立即 reject
    });
  });
};

/**
 * Promise.race 实现
 * 任意一个完成就返回(成功或失败)
 * @param {Iterable} promises
 * @return {Promise}
 */
Promise.myRace = function (promises) {
  return new Promise((resolve, reject) => {
    const promiseArray = Array.from(promises);

    // 空数组永远 pending(符合规范)
    for (const promise of promiseArray) {
      Promise.resolve(promise).then(resolve, reject);
    }
  });
};

/**
 * Promise.allSettled 实现
 * 等待所有 Promise 完成(无论成功失败)
 * @param {Iterable} promises
 * @return {Promise}
 */
Promise.myAllSettled = function (promises) {
  return new Promise((resolve) => {
    const promiseArray = Array.from(promises);
    const results = new Array(promiseArray.length);
    let settledCount = 0;

    if (promiseArray.length === 0) {
      resolve(results);
      return;
    }

    promiseArray.forEach((promise, index) => {
      Promise.resolve(promise)
        .then(
          (value) => {
            results[index] = { status: 'fulfilled', value };
          },
          (reason) => {
            results[index] = { status: 'rejected', reason };
          }
        )
        .finally(() => {
          settledCount++;
          if (settledCount === promiseArray.length) {
            resolve(results);
          }
        });
    });
  });
};

/**
 * Promise.any 实现(补充)
 * 任意一个成功就返回,全部失败才 reject
 */
Promise.myAny = function (promises) {
  return new Promise((resolve, reject) => {
    const promiseArray = Array.from(promises);
    const errors = new Array(promiseArray.length);
    let rejectedCount = 0;

    if (promiseArray.length === 0) {
      reject(new AggregateError([], 'All promises were rejected'));
      return;
    }

    promiseArray.forEach((promise, index) => {
      Promise.resolve(promise).then(resolve, (reason) => {
        errors[index] = reason;
        rejectedCount++;

        if (rejectedCount === promiseArray.length) {
          reject(new AggregateError(errors, 'All promises were rejected'));
        }
      });
    });
  });
};

示例调用

javascript
// ========== Promise.all ==========
// 基础示例:全部成功
const allResult = await Promise.myAll([
  Promise.resolve(1),
  Promise.resolve(2),
  new Promise((r) => setTimeout(() => r(3), 100)),
]);
console.log(allResult); // [1, 2, 3]

// 边界条件:空数组
console.log(await Promise.myAll([])); // []

// 边界条件:包含非 Promise 值
console.log(await Promise.myAll([1, 2, 3])); // [1, 2, 3]

// 边界条件:任一失败
try {
  await Promise.myAll([
    Promise.resolve(1),
    Promise.reject('error'),
    Promise.resolve(3),
  ]);
} catch (e) {
  console.log(e); // 'error'
}

// ========== Promise.race ==========
// 基础示例
const raceResult = await Promise.myRace([
  new Promise((r) => setTimeout(() => r('slow'), 200)),
  new Promise((r) => setTimeout(() => r('fast'), 100)),
]);
console.log(raceResult); // 'fast'

// 边界条件:有 reject
const raceReject = await Promise.myRace([
  new Promise((r) => setTimeout(() => r('slow'), 200)),
  Promise.reject('error'),
]).catch((e) => e);
console.log(raceReject); // 'error'

// ========== Promise.allSettled ==========
const settledResult = await Promise.myAllSettled([
  Promise.resolve(1),
  Promise.reject('error'),
  Promise.resolve(3),
]);
console.log(settledResult);
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: 'error' },
//   { status: 'fulfilled', value: 3 }
// ]

复杂度分析

  • 时间: O(n)
  • 空间: O(n)

10. 请求并发控制

难度: ⭐⭐⭐ | 梯队: 第二梯队 | 标签: 异步, 队列

题目描述

批量上传图片、批量请求接口时需限制并发数,避免浏览器连接数超限或服务端压力过大。实现类似 p-limit 的并发控制函数:创建一个执行器,最多同时运行 concurrency 个异步任务,超出部分进入队列等待。

代码实现

javascript
/**
 * 请求并发控制
 * @param {number} concurrency - 最大并发数
 * @return {Function} - 返回 (fn) => (...args) => Promise,包装后的函数
 */
function pLimit(concurrency) {
  if (concurrency < 1) {
    throw new Error('concurrency must be >= 1');
  }

  let activeCount = 0;
  const queue = [];

  const run = async (fn, resolve, reject, ...args) => {
    activeCount++;

    try {
      const result = await fn(...args);
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      activeCount--;
      // 队列中有等待的任务,取出执行
      if (queue.length > 0) {
        const next = queue.shift();
        run(next.fn, next.resolve, next.reject, ...next.args);
      }
    }
  };

  const enqueue = (fn, resolve, reject, ...args) => {
    queue.push({ fn, resolve, reject, args });
  };

  const limit = (fn) => {
    return function (...args) {
      return new Promise((resolve, reject) => {
        if (activeCount < concurrency) {
          run(fn, resolve, reject, ...args);
        } else {
          enqueue(fn, resolve, reject, ...args);
        }
      });
    };
  };

  return limit;
}

/**
 * 直接执行任务的版本
 * limit((url) => fetch(url)) 返回一个函数,调用时自动限流
 */
function pLimitDirect(concurrency) {
  let activeCount = 0;
  const queue = [];

  const run = async (task) => {
    activeCount++;
    try {
      const result = await task();
      return result;
    } finally {
      activeCount--;
      if (queue.length > 0) {
        const next = queue.shift();
        run(next);
      }
    }
  };

  return (task) => {
    return new Promise((resolve, reject) => {
      const wrapped = () => Promise.resolve(task()).then(resolve, reject);
      if (activeCount < concurrency) {
        run(wrapped);
      } else {
        queue.push(wrapped);
      }
    });
  };
}

示例调用

javascript
// 模拟异步任务
const delay = (ms, value) =>
  new Promise((r) => setTimeout(() => r(value), ms));

const limit = pLimit(2);

// 包装异步函数
const limitedFetch = limit(async (id) => {
  console.log(`start ${id}`);
  await delay(100, id);
  console.log(`end ${id}`);
  return id;
});

// 同时最多 2 个在执行
Promise.all([
  limitedFetch(1),
  limitedFetch(2),
  limitedFetch(3),
  limitedFetch(4),
]).then((results) => console.log(results)); // [1, 2, 3, 4]
// 输出顺序: start 1, start 2, end 1, start 3, end 2, start 4, end 3, end 4

// 边界条件:并发数为 1(串行)
const limit1 = pLimit(1);
const serial = limit1((x) => delay(50, x));
Promise.all([serial(1), serial(2), serial(3)]).then(console.log); // [1, 2, 3]

// 边界条件:任务失败不影响其他任务
const limit2 = pLimit(2);
const mayFail = limit2(async (x) => {
  if (x === 2) throw new Error('fail');
  return x;
});
Promise.allSettled([
  mayFail(1),
  mayFail(2),
  mayFail(3),
]).then(console.log);
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: Error: fail },
//   { status: 'fulfilled', value: 3 }
// ]

复杂度分析

  • 时间: O(n),n 为任务数
  • 空间: O(concurrency),队列最大长度为待执行任务数

前端面试知识库