Использование Web Workers
Web Worker-ы предоставляют простое средство для запуска скриптов в фоновом потоке. Поток Worker'а может выполнять задачи без вмешательства в пользовательский интерфейс. К тому же, они могут осуществлять ввод/вывод, используя XMLHttpRequest
(хотя атрибуты responseXML
и channel
всегда будут равны null). Существующий Worker может отсылать сообщения JavaScript коду-создателю через обработчик событий, указанный этим кодом (и наоборот). Эта статья даёт детальную инструкцию по использованию Web Workers.
Web Workers API
Worker - это объект, создаваемый конструктором (например, Worker()
) и запускающий именной JavaScript файл — этот файл содержит код, который будет выполнен в потоке Worker'а; объекты же Workers запускаются в другом глобальном контексте, отличающемся от текущего, - window
. Поэтому использование переменной window
для получения текущего глобального контекста (вместо self
) внутри Worker
вернёт ошибку.
Контекст Worker'а представлен объектом DedicatedWorkerGlobalScope
в случае выделенных Workers (обычные Workers используются одним скриптом; совместные Workers используют объект SharedWorkerGlobalScope
). Выделенный Worker доступен только из скрипта-родителя, в то время как совместные Workers могут быть доступны из нескольких сценариев.
Примечание: Смотрите страницу Web Workers API для справки по Workers и прочие руководства.
Вы можете запускать любой код внутри потока worker-а, за некоторыми исключениями. Например, вы не можете прямо манипулировать DOM внутри worker-а, или использовать некоторые методы по умолчанию и свойства объекта window
. Но вы можете использовать большой набор опций, доступный под Window
, включая WebSockets, и механизмы хранения данных, таких как IndexedDB и относящихся только к Firefox OS Data Store API. Для дополнительной информации смотрите Functions and classes available to workers.
Данные передаются между worker-ами и главным потоком через систему сообщений — обе стороны передают свои сообщения, используя метод postMessage()
и отвечают на сообщения при помощи обработчика событий onmessage
(сообщение хранится в атрибуте data события Message
). Данные при этом копируются, а не делятся.
Объекты Workers могут, в свою очередь, создавать новые объекты workers, и так до тех пор, пока всё работает в рамках текущей страницы. Плюс к этому, объекты workers могут использовать XMLHttpRequest
для сетевого ввода/вывода, но есть исключение - атрибуты responseXML
и channel
объекта XMLHttpRequest
всегда возвращают null
.
Выделенные Workers
Как уже упоминалось выше, выделенный Worker доступен только для скрипта, который его вызвал. В этом разделе речь пойдёт о JavaScript, который можно найти в нашем основном примере выделенного Worker (запустить скрипт): этот пример позволяет ввести два числа для умножения. Эти числа отправляются в Worker, перемножаются, а результат возвращается на страницу и отображается.
Этот пример достаточно тривиален, но для ознакомления с базовыми концепциями worker-ов мы решили его упростить. Более продвинутые детали описаны далее в статье.
Определение поддержки Worker
Для большего контроля над ошибками и обратной совместимости, рекомендуется обернуть ваш код доступа к worker-у в следующий (main.js):
if (window.Worker) {
...
}
Создание выделенного worker
Передача сообщений в и из выделенного worker
Магия worker-ов происходит через postMessage()
метод и обработчик событий onmessage
. Когда вы хотите отправить сообщение в worker, вы доставляете сообщение к нему вот так (main.js):
first.onchange = function () {
myWorker.postMessage([first.value, second.value]);
console.log("Message posted to worker");
};
second.onchange = function () {
myWorker.postMessage([first.value, second.value]);
console.log("Message posted to worker");
};
В приведённом фрагменте кода мы имеем два <input>
элемента, представленных переменными first
и second
; когда значение любой из переменных изменяется, myWorker.postMessage([first.value,second.value])
используется для отправки обоих значений, представленных в виде массива, в worker. Посредством аргумента message
возможна передача практически любых данных в worker.
Внутри worker-a мы можем обрабатывать сообщения и отвечать на них при помощи добавления обработчика события onmessage
подобным образом (worker.js):
onmessage = function (e) {
console.log("Message received from main script");
var workerResult = "Result: " + e.data[0] * e.data[1];
console.log("Posting message back to main script");
postMessage(workerResult);
};
Обработчик onmessage
позволяет нам запустить некий код всякий раз, когда получен пакет с сообщением, доступным в атрибуте data
события message
. В примере выше мы просто перемножаем вместе две цифры, после чего используем postMessage()
снова, чтобы отправить полученный результат назад в основной поток.
Возвращаясь в основной поток, мы используем onmessage
снова, чтобы отреагировать на сообщение, отправленное нам назад из worker-а:
myWorker.onmessage = function (e) {
result.textContent = e.data;
console.log("Message received from worker");
};
В примере выше мы берём данные из события сообщения и ставим их как textContent
у результирующего абзаца, чтобы показать пользователю результат этой калькуляции.
Примечание:
Обратите внимание, что onmessage()
и postmessage()
должны вызываться из экземпляра Worker в главном потоке, но не в потоке worker-а. Это связано с тем, что внутри потока worker-а, worker выступает в качестве глобального объекта.
Примечание: При передаче сообщения между основным потоком и потоком worker-а, оно копируется или "передаётся" (перемещается), не делится между потоками. Читайте Transferring data to and from workers: further details для более подробного объяснения.
Завершение работы worker-а
Прекращение работы worker-а главного потока достигается методом terminate
:
myWorker.terminate();
Поток worker-а немедленно уничтожается.
Обработка ошибок
При ошибке во время выполнения worker-а, вызывается его обработчик событий onerror
. Он принимает событие error
, которое реализует интерфейс ErrorEvent
.
Событие не всплывает и его можно отменить. Для отмены действия по умолчанию, worker может вызвать метод preventDefault()
в обработчике события ошибки.
У события ошибки есть три поля, которые представляют интерес:
Создание subworkers
Worker-ы могут запускать другие worker-ы. Так называемые sub-worker'ы должны быть того же происхождения (same-origin), что и родительский документ. Кроме того, URI для subworker-ов рассчитываются относительно родительского worker'а, а не родительского документа. Это позволяет worker-ам проще следить за тем, где находятся их зависимости.
Импорт скриптов и библиотек
Worker потоки имеют доступ к глобальной функции, importScripts()
, которая позволяет импортировать скрипты с того же домена в их область видимости. Функция принимает ноль и более URI параметров, как список ссылок на ресурсы для импорта; все нижеприведённые примеры верны:
importScripts(); /* imports nothing */
importScripts("foo.js"); /* imports just "foo.js" */
importScripts("foo.js", "bar.js"); /* imports two scripts */
Браузер загружает каждый указанный скрипт и исполняет его. Любые глобальные объекты, создаваемые каждым скриптом могут быть использованы в worker'е. Если скрипт не удалось загрузить, будет брошена ошибка NETWORK_ERROR
, и последующий код не будет исполнен. Тем не менее код, исполненный ранее (включая отложенный при помощи window.setTimeout()
) останется функционален. Объявления функций идущие после вызова метода importScripts()
также будут доступны, т.к. объявления функций всегда обрабатываются перед остальным кодом.
Примечание:
Скрипты могут быть загружены в произвольном порядке, но их исполнение будет в том порядке, в котором имена файлов были переданы в importScripts()
. Функция выполняется синхронно; importScripts()
не вернёт исполнение, пока все скрипты не будут загружены и исполнены.
Разделяемые worker-ы (Shared workers)
Разделяемый worker доступен нескольким разным скриптам — даже если они находятся в разных окнах, фреймах или даже worker-ах. В этом разделе мы обсудим JavaScript, который можно найти в нашем базовом примере разделяемых worker-ов (запустить разделяемый worker): Он очень похож на базовый пример выделенных worker-ов, за исключением двух функций, которые доступны из разных скриптовых файлов: умножение двух чисел или возведение числа в степень. Оба скрипта используют один и тот же worker для необходимых вычислений.
Здесь мы сосредоточимся на разнице между выделенными и разделёнными worker-ами. Обратите внимание, что в данном примере есть две HTML страницы с JavaScript-кодом, которые используют один и тот же файл worker-а.
Примечание: Если разделяемый worker может быть доступен из нескольких контекстов просмотра, то все они должны иметь одно и то же происхождение (одни и те же протокол, хост и порт).
**Примечание:**В Firefox разделяемый worker не может быть использован совместно документами в приватном и неприватном окне (Firefox bug 1177621).
Создание разделяемого worker-а
Запуск разделяемого worker-а очень похож на запуск выделенного worker-а, но используется другой конструктор (см. index.html и index2.html) — в каждом документе необходимо поднять worker, для этого следует написать такой код:
var myWorker = new SharedWorker("worker.js");
Большая разница заключается в том, что с разделяемым worker-ом необходимо взаимодействовать через объект port
— явно открыв порт, с помощью которого скрипты могут взаимодействовать с worker-ом (в случае выделенного worker-а это происходит неявно).
Соединение с портом должно быть осуществлено либо неявно, используя обработчик событие onmessage
, либо явно, вызвав метод start() перед тем, как отправлять любые сообщения. Вызов метода start() необходим только тогда, когда подписка на событие реализована через метод addEventListener()
.
**Примечание:**Когда используется метод
start()
чтобы открыть соединение с портом, его необходимо вызывать и в родительском потоке и в потоке worker-а, если необходима двухсторонняя коммуникация.
myWorker.port.start(); // в родительском потоке
port.start(); // в потоке worker-а, где переменная port является ссылкой на порт
Передача сообщений в/из разделяемого worker-а
Теперь сообщения могут быть отправлены worker-у, как и прежде, но метод postMessage()
должен вызываться из объекта port
(ещё раз, вы можете увидеть схожие конструкции в multiply.js и square.js):
squareNumber.onchange = function () {
myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
console.log("Message posted to worker");
};
Теперь на стороне worker-а. Здесь код немного сложнее (worker.js):
self.addEventListener("connect", function (e) {
// требуется addEventListener()
var port = e.ports[0];
port.onmessage = function (e) {
var workerResult = "Result: " + e.data[0] * e.data[1];
port.postMessage(workerResult);
};
port.start(); // вызов необязательный, т.к. используется обработчик событий onmessage
});
Первый этап состоит из события onconnect
. Оно срабатывает, когда произошло подключение (т.е. когда в родительском потоке отработало событие onmessage
или когда в нем был вызван метод start()
).
Мы используем атрибут события ports
, чтобы получить порт и сохранить его в переменной.
Второй этап — это обработчик события message
на сохранённом порту. Он нужен для подсчёта и вывода результата вычисления в основной поток. Установка обработчика message
в потоке worker-а также открывает подключение к родительскому потоку, поэтому вызов на port.start()
на самом деле не нужен (см. код обработчика onconnect
).
Последний этап — возвращение в основной поток и обработка сообщения от worker‑а (ещё раз, вы можете увидеть схожие конструкции в multiply.js и square.js):
myWorker.port.onmessage = function (e) {
result2.textContent = e.data[0];
console.log("Message received from worker");
};
Когда сообщение приходит через порт от worker-а, мы проверяем тип результата вычислений и затем вставляем его в соответствующий абзац.
О потоковой безопасности
Интерфейс Worker
создаёт настоящие потоки на уровне операционной системы, что может смутить опытных программистов и навести их на мысли о проблемах, связанных с конфликтом доступа к общим объектам.
На самом деле создать такие проблемы достаточно сложно, так как worker-ы жёстко контролируются. У них нет доступа к непотокобезопасным объектам DOM, а все данные между потоками передаются в качестве сериализованных объектов. Придётся очень постараться, чтобы вызывать проблемы потокобезопасности в вашем коде.
Передача данных в и из worker-ов: другие детали
Передача данных между главной страницей и worker-ом происходит путём копирования, а не передачи по ссылке. Объекты сериализуются при передаче и затем десериализуются на другом конце. Страница и worker не используют совместно одни и те же экземпляры, для каждого создаётся свой. Большинство браузеров реализуют это структурированным клонированием (structured cloning).
Для иллюстрации этого мы создадим функцию emulateMessage()
, которая будет имитировать поведение значения, которое клонируется, но не используется совместно при переходе от worker-а к главной странице или наоборот.
function emulateMessage(vVal) {
return eval("(" + JSON.stringify(vVal) + ")");
}
// Tests
// test #1
var example1 = new Number(3);
console.log(typeof example1); // object
console.log(typeof emulateMessage(example1)); // number
// test #2
var example2 = true;
console.log(typeof example2); // boolean
console.log(typeof emulateMessage(example2)); // boolean
// test #3
var example3 = new String("Hello World");
console.log(typeof example3); // object
console.log(typeof emulateMessage(example3)); // string
// test #4
var example4 = {
name: "John Smith",
age: 43,
};
console.log(typeof example4); // object
console.log(typeof emulateMessage(example4)); // object
// test #5
function Animal(sType, nAge) {
this.type = sType;
this.age = nAge;
}
var example5 = new Animal("Cat", 3);
alert(example5.constructor); // Animal
alert(emulateMessage(example5).constructor); // Object
Значения, которые клонируются и совместно не используются, называются сообщениями. Как вы, возможно, знаете, сообщения могут быть отправлены в главную страницу и из неё, используя postMessage()
, и data
, содержа данные, передаваемые из worker-а.
example.html: (главная страница):
var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
console.log("Worker said : " + oEvent.data);
};
myWorker.postMessage("ali");
my_task.js (worker):
postMessage("I'm working before postMessage('ali').");
onmessage = function (oEvent) {
postMessage("Hi " + oEvent.data);
};
Алгоритм структурированного клонирования может принять JSON и некоторые вещи, которые JSON не может принять, например, циклические ссылки.
Примеры передачи данных
Пример #1: Расширенная передача JSON данных и создание системы коммутации
Если вам нужно передать сложные данные и вызвать множество различных функций как на главной странице, так и в worker-е, вы можете создать следующую систему.
В первую очередь мы создаём класс QueryableWorker, который принимает url worker-а, стандартный обработчик событий (defaultListener) и обработчик ошибок. Этот класс будет отслеживать всех обработчиков и поможет нам общаться с воркером.
function QueryableWorker(url, defaultListener, onError) {
var instance = this,
worker = new Worker(url),
listeners = {};
this.defaultListener = defaultListener || function () {};
if (onError) {
worker.onerror = onError;
}
this.postMessage = function (message) {
worker.postMessage(message);
};
this.terminate = function () {
worker.terminate();
};
}
Затем мы добавляем методы добавления/удаления обработчиков.
this.addListeners = function (name, listener) {
listeners[name] = listener;
};
this.removeListeners = function (name) {
delete listeners[name];
};
Здесь мы создадим у worker-а два простых события для примера: получение разницы двух чисел и создание оповещения через три секунды. Но сначала нам нужно реализовать метод sendQuery, который проверит есть ли вообще у worker-а обработчик, который мы собираемся вызвать.
/*
Эта функция принимает по крайней мере один аргумент: имя метода, который мы хотим вызвать.
Далее мы можем передать методу необходимые ему аргументы.
*/
this.sendQuery = function () {
if (arguments.length < 1) {
throw new TypeError(
"QueryableWorker.sendQuery takes at least one argument",
);
return;
}
worker.postMessage({
queryMethod: arguments[0],
queryArguments: Array.prototype.slice.call(arguments, 1),
});
};
Завершим QueryableWorker методом onmessage
. Если worker имеет соответствующий метод, который мы запросили, он также должен вернуть соответствующий обработчик и аргументы, которые нам нужны. Останется лишь найти его в listeners
:
worker.onmessage = function (event) {
if (
event.data instanceof Object &&
event.data.hasOwnProperty("queryMethodListener") &&
event.data.hasOwnProperty("queryMethodArguments")
) {
listeners[event.data.queryMethodListener].apply(
instance,
event.data.queryMethodArguments,
);
} else {
this.defaultListener.call(instance, event.data);
}
};
Теперь к самому worker-у. Сначала следует определить эти два простых метода:
var queryableFunctions = {
getDifference: function (a, b) {
reply("printStuff", a - b);
},
waitSomeTime: function () {
setTimeout(function () {
reply("doAlert", 3, "seconds");
}, 3000);
},
};
function reply() {
if (arguments.length < 1) {
throw new TypeError("reply - takes at least one argument");
return;
}
postMessage({
queryMethodListener: arguments[0],
queryMethodArguments: Array.prototype.slice.call(arguments, 1),
});
}
/* This method is called when main page calls QueryWorker's postMessage method directly*/
function defaultReply(message) {
// do something
}
И onmessage
:
onmessage = function (event) {
if (
event.data instanceof Object &&
event.data.hasOwnProperty("queryMethod") &&
event.data.hasOwnProperty("queryMethodArguments")
) {
queryableFunctions[event.data.queryMethod].apply(
self,
event.data.queryMethodArguments,
);
} else {
defaultReply(event.data);
}
};
Полный код примера:
example.html (основная страница):
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Queryable worker</title>
<script type="text/javascript">
/*
QueryableWorker instances methods:
* sendQuery(queryable function name, argument to pass 1, argument to pass 2, etc. etc): calls a Worker's queryable function
* postMessage(string or JSON Data): see Worker.prototype.postMessage()
* terminate(): terminates the Worker
* addListener(name, function): adds a listener
* removeListener(name): removes a listener
QueryableWorker instances properties:
* defaultListener: the default listener executed only when the Worker calls the postMessage() function directly
*/
function QueryableWorker(url, defaultListener, onError) {
var instance = this,
worker = new Worker(url),
listeners = {};
this.defaultListener = defaultListener || function () {};
if (onError) {
worker.onerror = onError;
}
this.postMessage = function (message) {
worker.postMessage(message);
};
this.terminate = function () {
worker.terminate();
};
this.addListener = function (name, listener) {
listeners[name] = listener;
};
this.removeListener = function (name) {
delete listeners[name];
};
/*
This functions takes at least one argument, the method name we want to query.
Then we can pass in the arguments that the method needs.
*/
this.sendQuery = function () {
if (arguments.length < 1) {
throw new TypeError(
"QueryableWorker.sendQuery takes at least one argument",
);
return;
}
worker.postMessage({
queryMethod: arguments[0],
queryMethodArguments: Array.prototype.slice.call(arguments, 1),
});
};
worker.onmessage = function (event) {
if (
event.data instanceof Object &&
event.data.hasOwnProperty("queryMethodListener") &&
event.data.hasOwnProperty("queryMethodArguments")
) {
listeners[event.data.queryMethodListener].apply(
instance,
event.data.queryMethodArguments,
);
} else {
this.defaultListener.call(instance, event.data);
}
};
}
// your custom "queryable" worker
var myTask = new QueryableWorker("my_task.js");
// your custom "listeners"
myTask.addListener("printStuff", function (result) {
document
.getElementById("firstLink")
.parentNode.appendChild(
document.createTextNode("The difference is " + result + "!"),
);
});
myTask.addListener("doAlert", function (time, unit) {
alert("Worker waited for " + time + " " + unit + " :-)");
});
</script>
</head>
<body>
<ul>
<li>
<a
id="firstLink"
href="javascript:myTask.sendQuery('getDifference', 5, 3);"
>What is the difference between 5 and 3?</a
>
</li>
<li>
<a href="javascript:myTask.sendQuery('waitSomeTime');"
>Wait 3 seconds</a
>
</li>
<li>
<a href="javascript:myTask.terminate();">terminate() the Worker</a>
</li>
</ul>
</body>
</html>
my_task.js (код worker-а):
var queryableFunctions = {
// пример #1: получить разницу между двумя числами
getDifference: function (nMinuend, nSubtrahend) {
reply("printStuff", nMinuend - nSubtrahend);
},
// пример #2: подождать три секунды
waitSomeTime: function () {
setTimeout(function () {
reply("doAlert", 3, "seconds");
}, 3000);
},
};
// системные функции
function defaultReply(message) {
// your default PUBLIC function executed only when main page calls the queryableWorker.postMessage() method directly
// do something
}
function reply() {
if (arguments.length < 1) {
throw new TypeError("reply - not enough arguments");
return;
}
postMessage({
queryMethodListener: arguments[0],
queryMethodArguments: Array.prototype.slice.call(arguments, 1),
});
}
onmessage = function (oEvent) {
if (
oEvent.data instanceof Object &&
oEvent.data.hasOwnProperty("queryMethod") &&
oEvent.data.hasOwnProperty("queryMethodArguments")
) {
queryableFunctions[oEvent.data.queryMethod].apply(
self,
oEvent.data.queryMethodArguments,
);
} else {
defaultReply(oEvent.data);
}
};
Можно переключать содержимое каждой главной страницы -> worker и worker -> сообщение главной страницы. И имена свойств "queryMethod", "queryMethodListeners", "queryMethodArguments" могут быть любыми пока они согласуются с QueryableWorker
и worker
.
Передача данных с помощью передачи владения (передаваемые объекты)
Google Chrome 17+ and Firefox 18+ имеют дополнительную возможность передачи определённых типов объектов (передаваемые объекты реализующие Transferable
интерфейс) к или из worker-а с высокой производительностью. Эти объекты передаются из одного контекста в другой без операций копирования, что приводит к значительному повышению производительности при отправке больших наборов данных. Думайте об этом как о передаче по ссылке в мире C/C++. Однако в отличии от передачи по ссылке, "версия" из вызывающего контекста больше недоступна после передачи. Владельцем становится новый контекст. Для примера, после передачи ArrayBuffer
из главной страницы к worker-у, исходный ArrayBuffer
очищается и более недоступен для использования. Его содержание (в буквальном смысле) переносится в рабочий контекст.
// Create a 32MB "file" and fill it.
var uInt8Array = new Uint8Array(1024 * 1024 * 32); // 32MB
for (var i = 0; i < uInt8Array.length; ++i) {
uInt8Array[i] = i;
}
worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
Примечание: Для дополнительной информации о передаваемых объектах, производительности и поддержки для этого метода, читайте Transferable Objects: Lightning Fast! на HTML5 Rocks.
Встроенные worker-ы
Не существует утверждённого способа встроить код worker-а в рамках веб-страницы, как элемент <script>
делает для обычных скриптов. Но элемент <script>
, который не имеет атрибута src
и атрибута type
, которому не назначен выполняемый MIME type, можно считать блоком данных для использования JavaScript. Блок данных "Data blocks" — это более общее свойство HTML5, может содержать любые текстовые данные. Так, worker может быть встроен следующим образом:
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>MDN Example - Embedded worker</title>
<script type="text/js-worker">
// Этот script НЕ БУДЕТ анализироваться JS движками, потому что его MIME-тип text/js-worker.
var myVar = 'Hello World!';
// Остальная часть кода вашего воркера идёт сюда.
</script>
<script type="text/javascript">
// Этот script БУДЕТ проанализирован JS движкам, потому что его MIME-тип text/javascript.
function pageLog(sMsg) {
// Use a fragment: browser will only render/reflow once.
var oFragm = document.createDocumentFragment();
oFragm.appendChild(document.createTextNode(sMsg));
oFragm.appendChild(document.createElement("br"));
document.querySelector("#logDisplay").appendChild(oFragm);
}
</script>
<script type="text/js-worker">
// Этот script НЕ БУДЕТ анализироваться JS движками, потому что его MIME-тип text/js-worker.
onmessage = function(oEvent) {
postMessage(myVar);
};
// Остальная часть кода вашего воркера идёт сюда.
</script>
<script type="text/javascript">
// Этот script БУДЕТ проанализирован JS движкам, потому что его MIME-тип text/javascript.
// В прошлом...:
// blob builder существовал
// ... но теперь мы используем Blob...:
var blob = new Blob(
Array.prototype.map.call(
document.querySelectorAll("script[type='text\/js-worker']"),
function (oScript) {
return oScript.textContent;
},
),
{ type: "text/javascript" },
);
// Создание нового свойства document.worker, содержащего все наши "text/js-worker" скрипты.
document.worker = new Worker(window.URL.createObjectURL(blob));
document.worker.onmessage = function (oEvent) {
pageLog("Received: " + oEvent.data);
};
// Запуск воркера.
window.onload = function () {
document.worker.postMessage("");
};
</script>
</head>
<body>
<div id="logDisplay"></div>
</body>
</html>
Встраиваемый worker теперь внесён в новое custom свойство document.worker
Также стоит отметить, что вы также можете преобразовать функцию в BLOB-объект, а затем сгенерировать URL объекта из этого BLOB-объекта. Например:
function fn2workerURL(fn) {
var blob = new Blob(["(" + fn.toString() + ")()"], {
type: "application/javascript",
});
return URL.createObjectURL(blob);
}
Другие примеры
В этой секции представлено ещё несколько примеров как использовать worker-ы.
Выполнение вычислений в фоне
Worker-ы в основном полезны для того, чтобы позволить вашему коду выполнять ресурсоёмкие вычисления, не блокируя поток пользовательского интерфейса. В этом примере, worker используется для вычисления числа Фибоначчи.
Код JavaScript
Следующий код JavaScript хранится в файле "fibonacci.js", на который ссылается HTML в следующем разделе.
var results = [];
function resultReceiver(event) {
results.push(parseInt(event.data));
if (results.length == 2) {
postMessage(results[0] + results[1]);
}
}
function errorReceiver(event) {
throw event.data;
}
onmessage = function (event) {
var n = parseInt(event.data);
if (n == 0 || n == 1) {
postMessage(n);
return;
}
for (var i = 1; i <= 2; i++) {
var worker = new Worker("fibonacci.js");
worker.onmessage = resultReceiver;
worker.onerror = errorReceiver;
worker.postMessage(n - i);
}
};
Worker устанавливает свойство onmessage
для функции, которая будет получать сообщения, отправленные при вызове postMessage()
рабочего объекта (обратите внимание, что это отличается от определения глобальной переменной с таким именем или определения функции с таким именем. var onmessage
и function onmessage
будет определять глобальные свойства с этими именами , но они не будут регистрировать функцию для получения сообщений, отправленных веб-страницей, которая создала worker). Это запускает рекурсию, порождая новые копии для обработки каждой итерации вычисления.
HTML код
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Test threads fibonacci</title>
</head>
<body>
<div id="result"></div>
<script language="javascript">
var worker = new Worker("fibonacci.js");
worker.onmessage = function (event) {
document.getElementById("result").textContent = event.data;
dump("Got: " + event.data + "\n");
};
worker.onerror = function (error) {
dump("Worker error: " + error.message + "\n");
throw error;
};
worker.postMessage("5");
</script>
</body>
</html>
Веб-страница создаёт элемент div
с ID result
, который используется для отображения результата, а затем порождает worker. После порождения worker-а, обработчик onmessage
настроен для отображения результатов путём установки содержимого элемента div
, и обработчик onerror
настроен на выброс сообщения об ошибке.
Наконец, сообщение отправляется worker-у, чтобы запустить его.
Выполнение веб I/O в фоне
Вы можете найти пример этого в статье Использование worker-ов в расширениях.
Разделение задач между множественными worker-ами
Поскольку многоядерные компьютеры становятся все более распространёнными, часто бывает полезно разделить вычислительно сложные задачи между несколькими worker-ами, которые затем могут выполнить эти задачи на многопроцессорных ядрах.
Другие типы worker-ов
В дополнение к выделенным и совместно используемым web worker-ам доступны другие типы worker-ов:
- ServiceWorkers, по сути, действуют как прокси-серверы, которые размещаются между веб-приложениями, браузером и сетью (при наличии). Они предназначены (помимо прочего) для создания эффективного автономного взаимодействия, перехвата сетевых запросов и принятия соответствующих действий в зависимости от того, доступна ли сеть, и обновлены ли ресурсы на сервере. Они также разрешают доступ push-уведомлениям и API фоновой синхронизации.
- Chrome Workers это worker типа Firefox-only, который вы можете использовать, если вы разрабатываете дополнения и хотите использовать worker-ы в расширениях и иметь доступ к js-ctypes в вашем worker-е. Смотрите
ChromeWorker
для более подробной информации. - Audio Workers предоставляют возможность прямой обработки звука по сценарию в контексте web worker-а.
Функции и интерфейсы доступные в worker-ах
Внутри web worker-а вы можете использовать большинство стандартных функций JavaScript, включая:
Navigator
XMLHttpRequest
Array
,Date
,Math
, иString
Window.requestAnimationFrame
,WindowTimers.setTimeout
, иWindowTimers.setInterval
Главное, что вы не можете сделать в Worker это напрямую повлиять на родительскую страницу. Это включает в себя манипулирование DOM и использование объектов этой страницы. Вы должны сделать это косвенно, отправив сообщение обратно основному сценарию через DedicatedWorkerGlobalScope.postMessage
, а затем выполнив изменения оттуда.
Примечание: Для знакомства с полным списком функций, доступных для worker-ов, смотрите статью Функции и интерфейсы доступные worker-ам.
Спецификации
Specification |
---|
HTML Standard # workers |
Смотрите также
- Интерфейс
Worker
- Интерфейс
SharedWorker
- Доступные для воркеров функции
- Интерфейс
OffscreenCanvas