There are a lot of ways to break up long tasks in JavaScript.

在前端开发中,处理长时间运行的任务是一个常见的挑战。长时间占用主线程会导致用户体验下降,例如页面无法及时响应用户的点击操作或无法正常更新显示内容。为了解决这一问题,开发者可以将长时间任务拆分为多个小任务,并在事件循环的多个时间点上执行,从而让浏览器有机会处理其他重要任务,如用户输入和页面重绘。

文章中介绍了多种在 JavaScript 中拆分长时间任务的方法,每种方法都有其特点和适用场景:

  1. setTimeout() + 递归:这是一种经典的解决方案,通过在 setTimeout() 的回调中递归调用自身,将任务拆分到不同的事件循环时间点上。这种方法简单易懂,但随着 ES6 的出现,有更好的替代方案。
  2. async/await + 延时:利用 async/awaitPromise,可以在一个简单的 for 循环中实现任务拆分。通过 await 一个立即解析的 Promise,可以让代码在每次迭代后暂停执行,从而释放事件循环。这种方法比递归更简洁,且易于理解。
  3. scheduler.postTask():这是 Chromium 浏览器中相对较新的 API,用于更高效地调度任务。它允许开发者控制任务的优先级,例如设置为“用户可见”或“背景”优先级。与 setTimeout() 不同,postTask() 调度的任务会被放置在任务队列的前面,避免被其他任务延迟执行。不过,该 API 的浏览器支持还不够广泛,但可以通过 polyfill 解决。
  4. scheduler.yield():该方法是 Scheduler 接口的一部分,专门用于在任务中释放主线程,并稍后继续执行。它允许开发者在长时间任务中暂停执行,让浏览器有机会处理其他任务,从而保持页面响应性。与 postTask() 一样,yield() 也需要 polyfill 来支持非 Chromium 浏览器。
  5. requestAnimationFrame():该 API 通常用于与浏览器的重绘周期同步执行任务。虽然它可以精确地调度回调,但在执行长时间任务时可能会对渲染性能产生负面影响,例如导致部分帧无法完整呈现。因此,它更适合用于与动画相关的任务,而不是拆分长时间任务。
  6. MessageChannel():通过创建一个消息通道并在通道中传递消息,可以在不依赖定时器的情况下实现任务拆分。这种方法的性能表现较好,但实现相对复杂,且并非其设计初衷。
  7. Web Workers:如果任务可以在主线程之外执行,Web Workers 是最佳选择。它们可以将任务完全移出主线程,从而避免对页面响应性的影响。虽然使用 Web Workers 时需要处理线程间通信的开销,但其对主线程的解压效果最为显著。

在选择适合的方法时,需要根据具体需求和浏览器支持情况来决定。如果任务可以在主线程之外执行,Web Workers 是首选;如果需要简单易用的解决方案,可以考虑 scheduler.yield()async/await + 延时;如果需要更精细的任务调度控制,则可以使用 scheduler.postTask()