如何在JavaScript中安排后台任务

如果您没有其他有关JavaScript的知识,请不要忘记这一点: 它阻塞了

想象一下神奇的处理小精灵可以使您的浏览器正常工作。 无论是呈现HTML,响应菜单命令,在屏幕上绘画,处理鼠标单击还是运行JavaScript函数,所有这些要素都由单个像素处理。 像我们大多数人一样,小精灵一次只能做一件事。 如果我们把很多任务扔给小精灵,它们会被添加到一个大的待办事项列表中并按顺序处理。

当小精灵遇到script标签或必须运行JavaScript函数时,其他所有操作都会停止。 该代码已下载(如果需要)并在可以处理其他事件或渲染之前立即运行。 这是必要的,因为您的脚本可以执行任何操作:加载更多代码,删除每个DOM元素,重定向到另一个URL等。即使有两个或多个小精灵,其他小精灵在第一个处理您的代码时也需要停止工作。 太难了。 这就是长时间运行的脚本导致浏览器无法响应的原因。

您通常希望JavaScript尽快运行,因为代码会初始化小部件和事件处理程序。 但是,有一些不太重要的后台任务不会直接影响用户体验,例如

  • 记录分析数据
  • 向社交网络发送数据(或添加57个“共享”按钮)
  • 预取内容
  • 预处理或预渲染HTML

这些不是时间紧迫的,但是为了使页面保持响应状态,它们不应在用户滚动或与内容交互时运行。

一种选择是使用Web Workers ,它可以在一个单独的线程中同时运行代码。 对于预取和处理而言,这是一个不错的选择,但不允许您直接访问或更新DOM。 您可以在自己的脚本中避免这种情况,但不能保证在第三方脚本(例如Google Analytics(分析))中永远不需要它。

另一种可能性是setTimeout ,例如setTimeout(doSomething, 1); 。 一旦其他立即执行的任务完成,浏览器将执行doSomething()函数。 实际上,它放在待办事项列表的底部。 不幸的是,无论处理需求如何,都会调用该函数。

requestIdleCallback

requestIdleCallback是一个新的API,旨在在浏览器喘口气的那些时刻安排不必要的后台任务。 这让人想起requestAnimationFrame ,后者在下一次重绘之前调用一个函数来更新动画。 您可以在此处阅读有关requestAnimationFrame更多信息: 使用requestAnimationFrame的简单动画

我们可以像这样检测是否支持requestIdleCallback

if ('requestIdleCallback' in window) {
  // requestIdleCallback supported
  requestIdleCallback(backgroundTask);
}
else {
  // no support - do something else
  setTimeout(backgroundTask1, 1);
  setTimeout(backgroundTask2, 1);
  setTimeout(backgroundTask3, 1);
}

您还可以指定带有超时(以毫秒为单位)的options对象参数,例如

requestIdleCallback(backgroundTask, { timeout: 3000; });

这样可确保在前三秒内调用您的函数,而不管浏览器是否处于空闲状态。

requestIdleCallback仅调用您的函数一次,并传递带有以下属性的deadline对象:

  • didTimeout —如果触发了可选超时,则设置为true
  • timeRemaining() —一个函数,返回执行任务剩余的毫秒数

timeRemaining()将为您的任务分配不超过50毫秒的时间。 它不会停止超过此限制的任务,但是最好您再次调用requestIdleCallback安排进一步处理。

让我们创建一个简单的示例,该示例按顺序执行几个任务。 任务作为函数引用存储在数组中:

// array of functions to run
var task = [
	background1,
	background2,
	background3
];

if ('requestIdleCallback' in window) {
  // requestIdleCallback supported
  requestIdleCallback(backgroundTask);
}
else {
  // no support - run all tasks soon
  while (task.length) {
  	setTimeout(task.shift(), 1);
  }
}

// requestIdleCallback callback function
function backgroundTask(deadline) {

  // run next task if possible
  while (deadline.timeRemaining() > 0 && task.length > 0) {
  	task.shift()();
  }

  // schedule further tasks if necessary
  if (task.length > 0) {
    requestIdleCallback(backgroundTask);
  }
}

有什么不应该在requestIdleCallback中完成的吗?

正如Paul Lewis在其有关该主题的博客文章中指出的那样,您在requestIdleCallback中所做的工作应该很小。 它不适用于执行时间无法预测的任何事情(例如,操作DOM,最好使用requestAnimationFrame回调来完成)。 您还应该警惕解决(或拒绝)Promises,因为即使没有更多时间了,在空闲回调完成后,回调也会立即执行。

requestIdleCallback浏览器支持

requestIdleCallback是一项实验性功能,规格仍在不断变化中,因此当您遇到API更改时不要感到惊讶。 Chrome 47…已支持该功能,该功能应于2015年底之前提供。Opera也应立即获得该功能。 微软和Mozilla都在考虑API,这听起来很有希望。 苹果一如既往地无言以对。 如果您想今天旋转一下,最好的选择是使用Chrome CanaryChrome的更新版本,虽然没有经过充分测试,但具有最新的亮点)。

Paul Lewis(如上所述)创建了一个简单的requestIdleCallback shim 。 这实现了所描述的API,但是它不是可以模拟浏览器的空闲检测行为的polyfill。 像上面的示例一样,它使用setTimeout ,但是如果要使用没有对象检测和代码分叉的API,这是一个不错的选择。

虽然今天的支持有限,但是requestIdleCallback可能是一种有趣的功能,可帮助您最大程度地提高网页性能。 但是你觉得呢? 我很高兴在下面的评论部分听到您的想法。

From: https://www.sitepoint.com/how-to-schedule-background-tasks-in-javascript/