本文由 Malte Ubl 撰写,他在 Bespin 项目中对 使用 Web Workers 做出了很多贡献。
近年来,Web 应用程序的用户体验越来越丰富。像 GMail、Meebo 和 Bespin 这样的浏览器内应用程序让我们看到了 Web 未来的样子和感觉。创建优秀用户体验的关键方面之一是构建高度响应的应用程序。用户讨厌等待,也讨厌应用程序在运行一段时间后停止响应用户输入的情况。
现代客户端 Web 应用程序的核心是 JavaScript 编程语言。JavaScript 及其所使用的 DOM 本质上是单线程的。这意味着在 JavaScript 中,一次只能发生一件事。即使您的计算机有 32 个核心,它在进行长时间计算时也只会使用其中一个核心。例如,如果您计算前往月球的最佳轨迹,它将无法同时渲染显示该轨迹的动画,也无法在进行计算时响应任何用户事件,例如点击或键盘输入。
并发
为了在执行密集计算时保持响应能力,并发 是大多数现代编程语言的一部分。过去,并发通常通过使用线程来实现。然而,线程使得程序员越来越难理解程序流程,这往往会导致难以理解的错误,以及不同线程同时操作相同数据时产生的混乱行为。
Web Workers 由 WHATWG 推荐,并于 Firefox 3.5 中引入,旨在为 JavaScript 应用程序添加并发性,而不会引入与多线程程序相关的那些问题。启动工作者很容易 - 只需使用新的 Worker 接口即可。
在此示例中,将加载 worker.js 文件,并创建一个新线程来执行该代码。
// Start worker from file "worker.js"
var worker = new Worker("worker.js");
主 UI 线程和工作者之间的通信通过使用 postMessage 方法传递消息来完成。postMessage 是在 Firefox 3 中添加用于跨窗口通信的。要从工作者发送消息回页面,只需发布消息即可
// Send a message back to the main UI thread
postMessage("Hello Page!");
要捕获来自工作者的消息,您需要在 worker 对象上定义一个“onmessage”回调。这里我们只是将传递给回调函数的事件数据进行提醒。在本例中,“event.data”包含上面发送的“Hello Page!”字符串。
worker.onmessage = function (event) {
alert(event.data);
// Send a message to the worker
worker.postMessage("Hello Worker");
}
要向工作者发送消息,我们调用 worker 对象上的 postMessage 方法。要在工作者内部接收这些消息,只需定义一个 onmessage 函数,该函数将在每次向工作者发布消息时被调用。
错误处理
您可以从工作者中发生的运行时错误中恢复,有两个级别。首先,您可以在工作者中定义一个 onerror 函数。其次,您可以通过在 worker 对象上附加 onerror 处理程序来从工作者外部处理错误
worker.onerror = function (event) {
alert(event.message);
event.preventDefault();
}
event.preventDefault() 方法阻止默认操作,默认操作会将错误显示给用户,或者至少将其显示在错误控制台中。这里我们改为提醒错误消息。
无共享
工作者与他们关联的页面或任何其他工作者绝对不共享任何状态;他们唯一可以交互的方式是通过 postMessage。工作者也无法访问 DOM,因此它们不能直接操作网页。因此,当多个工作者想要同时操作相同数据时,没有数据完整性问题的风险。
使用工作者的标准设置将包含一个页面 JavaScript 组件,该组件正在监听用户事件。当发生触发密集计算的事件时,会向工作者发送消息,工作者开始计算。然而,页面上的脚本可以立即终止并监听更多用户事件。一旦工作者完成,它就会向页面发送一条返回消息,然后页面可以例如显示结果。

当使用 Web Workers 时,浏览器在脚本执行时间过长时显示的无响应脚本警告已经成为过去。
斐波那契数列示例
接下来是一个工作者的示例,该工作者在后台计算从 0 到 99 的斐波那契数列。实际上,由于使用这种非常低效的方法计算斐波那契数列对于较大的数字(比如大于 30 的数字)可能需要很长时间(或者由于堆栈溢出而崩溃),但如果在工作者中执行,这对主网页的响应性没有影响。因此,您仍然可以绘制复杂的动画,使等待下一个数字的时间更有趣一些。
此 HTML 页面包含一个脚本,该脚本从文件“fib-worker.js”启动一个工作者。来自工作者的消息使用 console.log 显示在浏览器的控制台上。
Web Worker API Demo
实现工作者的 JavaScript 文件包含一个循环,该循环计算斐波那契数列并将结果发送到页面。
// File fib-worker.js
function fib(n) {
return n < 2 ? n : fib(n-1) + fib(n-2);
}
for(var i = 0; i < 100; ++i) {
postMessage({
index: i,
value: fib(i)
})
}
在上面的示例中,我们看到我们也可以将复杂对象传递给 postMessage。这些对象可以包含通过 JSON 传输的所有内容。这意味着函数不能跨工作者边界传递,并且对象是按值而不是按引用传递的。
工作者 API
工作者支持一个名为 importScripts 的函数。您可以使用它将更多源文件加载到工作者中。
importScripts("file.js");
importScripts("foo.js", "bar.js");
当您向函数传递多个参数时,脚本将并行下载,但按定义顺序执行。该函数在所有脚本都下载并执行之前不会返回。
这里我们加载一个外部 JavaScript 文件,该文件计算来自字符串的 SHA-1 哈希和,然后我们使用它来哈希来自 AJAX 请求的响应。我们还使用标准的 XMLHttpRequest 对象来检索通过 onmessage 事件传入的 URL 的内容。有趣的是,我们不必担心使 AJAX 请求异步,因为工作者本身相对于页面渲染是异步的,因此等待 HTTP 请求的时间不会那么令人担忧。
importScripts("sha1.js")
function onmessage(event) {
var xhr = new XMLHttpRequest();
xhr.open('GET', event.data, false);
xhr.send();
postMessage(sha1(xhr.responseText));
}
工作者可用的其他 API
工作者可以使用 XMLHttpRequest 进行 AJAX 请求,如上所述,并使用 Web 存储 API 访问客户端数据库。这里的 API 与在常规 JavaScript 中的使用方式相同。
setTimeout 和 setInterval(以及 clearTimeout 和 clearInterval 同伴),它们允许在给定时间段后或以特定间隔执行代码,在工作者中也可用,就像众所周知的 navigator 对象 一样,可以检查该对象以获取有关当前浏览器的信息。
将来可能会添加更多 API。
浏览器兼容性
截至本文撰写时(作者所知),Firefox 3.5 是唯一支持通过 postMessage 传递复杂对象以及实现上述扩展 API 的浏览器。Safari 4 实现了一个非常基本的 Worker API 版本。对于其他浏览器,可以通过 Google Gears 使用工作者,该工具最初将该概念引入到浏览器中。
现实世界中的应用
在 Bespin 项目 中,这是一个基于浏览器的源代码编辑器,我们成功地使用了工作者来实现 CPU 密集型功能,例如实时源代码错误检查和代码补全。我们还创建了 一个垫片,它根据 Google Gears 实现 Worker API,并将缺失的功能添加到 Safari 4 的工作者实现中,并且 迁移到在 postMessage 接口之上使用透明的自定义事件。这些组件将作为独立库发布,以便将来可以在其他项目中使用。
Web Workers 将在使开放 Web 成为更强大、更复杂的应用程序平台方面发挥重要作用。因为它们最终只是执行 JavaScript,所以很容易使脚本在还没有 Web Workers 的客户端上运行。因此,请立即将它们添加到您的应用程序中,使您的应用程序更具响应性,使用起来更愉快。
17 条评论