基于时间分片的累加求和

问题描述

请实现一个函数 calculateSum(n),用于计算从 1 到 n 的累加和。n 可能是一个非常大的数字(例如小于 1,000,000,000)。

为了避免因长时间运行的计算任务而阻塞浏览器主线程,导致页面卡顿,该函数必须采用时间分片 (Time Slicing) 机制。

核心要求:

  1. 分批处理: 函数应将整个累加任务分割成多个小批次。
  2. 时间限制: 每个批次的处理时间不应超过 15 毫秒。在一个批次中,应尽可能多地执行累加操作。
  3. 异步执行: 当一个批次执行超时后,应立即停止当前批次的计算,并通过 setTimeout 将剩余的任务推迟到下一个宏任务中执行,从而让出主线程,保障页面响应性。
  4. 返回结果: 由于计算是异步的,函数必须返回一个 Promise。当所有累加操作完成后,该 Promiseresolve 并返回最终的累加和。
  5. 禁用数学公式: 禁止直接通过数学公式(如 (n * (n + 1)) / 2)一次性计算出结果,必须通过模拟逐步累加的过程来完成。
  6. 处理大数: n 和最终的和可能会超过 JavaScript Number 类型的安全整数范围,需要使用 BigInt 进行计算。

函数签名

/**
 * 基于时间分片机制,计算从 1 到 n 的累加和。
 * @param {number} n - 一个正整数,上限可以很大。
 * @returns {Promise<bigint>} - 一个 Promise,在计算完成后解析为 BigInt 类型的总和。
 */
function calculateSum(n) {
  // TODO: 请实现代码
}

示例用法

calculateSum(1000000).then((sum) => {
  console.log('Sum:', sum);
  // 预期输出: Sum: 500000500000n
});

calculateSum(2000000000).then((sum) => {
  console.log('Large Sum:', sum);
  // 预期输出: Large Sum: 2000000001000000000n
});

解题思路

要实现带时间分片的异步计算,核心思想是将一个大的同步循环改造成多个小的、异步执行的单元。

  1. 异步返回: 由于计算过程被拆分到多个事件循环周期中,函数无法立即返回最终结果。因此,它必须返回一个 Promise,并在所有计算完成后 resolve 这个 Promise

  2. 状态管理: 我们需要在多个计算批次之间维护状态:

    • sum: 当前的累加和。由于结果会非常大,必须使用 BigInt (例如 0n)。
    • i: 当前累加到的数字。同样,使用 BigInt (例如 1n)。
    • n_big: 目标数字 nBigInt 版本,用于比较。
  3. 创建工作单元函数: 定义一个内部函数,例如 performChunk(),它将负责执行一个批次的计算。

  4. 时间分片逻辑: 在 performChunk() 函数内部:

    • 记录开始时间:使用 performance.now()Date.now() 获取当前时间戳作为批次的开始时间。
    • 执行循环:在一个 while 循环中持续进行累加操作 (sum += i; i++;)。
    • 设置跳出条件:while 循环的条件应该有两个:
      1. i <= n_big:确保计算没有完成。
      2. performance.now() - startTime < 15:检查从批次开始到现在所经过的时间是否在 15 毫秒的限制内。
    • 当循环因为超时而跳出时,说明这个批次的工作已经完成。
  5. 调度下一个任务:

    • performChunk() 的末尾,检查计算是否已全部完成(即 i > n_big)。
    • 如果已完成,调用 Promiseresolve 方法,并传入最终的 sum
    • 如果未完成,使用 setTimeout(performChunk, 0) 将下一次的 performChunk 调用安排到宏任务队列中。这会将控制权交还给主线程,允许浏览器处理渲染、用户交互等其他任务,然后在下一个事件循环中继续我们的计算。
  6. 启动计算: 在 calculateSum 函数的主体中,调用一次 performChunk() 来启动整个计算流程。

这种“工作-检查-调度”的模式是实现时间分片、避免阻塞主线程的关键。

代码实现

/**
 * 基于时间分片机制,计算从 1 到 n 的累加和。
 * @param {number} n - 一个正整数,上限可以很大。
 * @returns {Promise<bigint>} - 一个 Promise,在计算完成后解析为 BigInt 类型的总和。
 */
function calculateSum(n) {
  // 函数立即返回一个 Promise
  return new Promise((resolve) => {
    // 使用 BigInt 来处理可能超出 Number 安全范围的大数
    let sum = 0n;
    let i = 1n;
    const n_big = BigInt(n);

    // 每个计算批次的时间限制(毫秒)
    const timeLimit = 15;

    /**
     * 执行一个计算批次的函数
     */
    function performChunk() {
      const startTime = performance.now();

      // 在时间限制内,并且未计算完成时,持续执行累加
      while (i <= n_big && performance.now() - startTime < timeLimit) {
        sum += i;
        i++;
      }

      // 检查是否所有累加都已完成
      if (i > n_big) {
        // 如果完成,则 resolve Promise 并返回最终结果
        resolve(sum);
      } else {
        // 如果未完成,将下一个计算批次推迟到宏任务队列中
        // 这会将控制权交还给主线程,避免页面卡顿
        setTimeout(performChunk, 0);
      }
    }

    // 启动第一个计算批次
    performChunk();
  });
}

// --- 测试用例 ---
console.log('开始计算 1 到 1,000,000 的和...');
calculateSum(1000000).then((sum) => {
  console.log('Sum (1 to 1,000,000):', sum); // 预期: 500000500000n
  console.log('预期结果与实际结果是否一致:', sum === 500000500000n);
});

console.log('开始计算 1 到 2,000,000,000 的和... (这可能需要一些时间)');
calculateSum(2000000000).then((sum) => {
  console.log('Sum (1 to 2,000,000,000):', sum); // 预期: 2000000001000000000n
});

// 在计算大数时,页面仍然可以响应
let count = 0;
const intervalId = setInterval(() => {
  console.log(`主线程没有被阻塞,这是第 ${++count} 次心跳检查。`);
}, 500);

// 当最大的计算任务完成时,停止心跳检查
calculateSum(2000000000).then(() => {
  clearInterval(intervalId);
  console.log('所有计算任务完成,心跳检查已停止。');
});
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""