Веб-работники без отдельного файла Javascript? - PullRequest
270 голосов
/ 23 марта 2011

Насколько я могу судить, веб-работники должны быть написаны в отдельном файле JavaScript и называться так:

new Worker('longrunning.js')

Я использую компилятор закрытия для объединения и минимизации всего моего исходного кода JavaScript, и я бы не хотел, чтобы мои работники размещались в отдельных файлах для распространения. Есть ли способ сделать это?

new Worker(function() {
    //Long-running work here
});

Учитывая, что первоклассные функции так важны для JavaScript, почему стандартный способ выполнения фоновой работы должен загружать целый «другой» файл JavaScript с сервера?

Ответы [ 24 ]

201 голосов
/ 23 июня 2011

http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers

Что если вы хотите создать свой рабочий скрипт на лету или создать автономную страницу без необходимости создания отдельных рабочих файлов? С помощью Blob () вы можете «встроить» своего работника в тот же HTML-файл, что и основная логика, создав URL-дескриптор рабочего кода в виде строки


Полный пример работника BLOB:

<!DOCTYPE html>
<script id="worker1" type="javascript/worker">
  // This script won't be parsed by JS engines because its type is javascript/worker.
  self.onmessage = function(e) {
    self.postMessage('msg from worker');
  };
  // Rest of your worker code goes here.
</script>
<script>
  var blob = new Blob([
    document.querySelector('#worker1').textContent
  ], { type: "text/javascript" })

  // Note: window.webkitURL.createObjectURL() in Chrome 10+.
  var worker = new Worker(window.URL.createObjectURL(blob));
  worker.onmessage = function(e) {
    console.log("Received: " + e.data);
  }
  worker.postMessage("hello"); // Start the worker.
</script>
149 голосов
/ 05 октября 2013

Решение html5rocks по встраиванию кода веб-работника в HTML довольно ужасно.
И капля с пробелом JavaScript-как-строка не лучше, не в последнюю очередь потому, что она усложняет рабочий процесс (компилятор Closure может 'не работает со строками).

Лично мне действительно нравятся методы toString, но @ dan-man ЭТО регулярное выражение!

Мой предпочтительный подход:

// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL( new Blob([ '(',

function(){
    //Long-running work here
}.toString(),

')()' ], { type: 'application/javascript' } ) ),

worker = new Worker( blobURL );

// Won't be needing this anymore
URL.revokeObjectURL( blobURL );

Поддержка - это пересечение этих трех таблиц:

Это не будет работать для SharedWorker , однако, потому что URL должен быть точным соответствием, даже если необязательный параметр 'name'Матчи.Для SharedWorker вам понадобится отдельный файл JavaScript.


2015 обновление - особенность ServiceWorker наступает

Теперь есть еще более эффективный способ решения этой проблемы.Опять же, сохраните рабочий код как функцию (а не статическую строку) и преобразуйте ее с помощью .toString (), затем вставьте код в CacheStorage под выбранным статическим URL-адресом.

// Post code from window to ServiceWorker...
navigator.serviceWorker.controller.postMessage(
 [ '/my_workers/worker1.js', '(' + workerFunction1.toString() + ')()' ]
);

// Insert via ServiceWorker.onmessage. Or directly once window.caches is exposed
caches.open( 'myCache' ).then( function( cache )
{
 cache.put( '/my_workers/worker1.js',
  new Response( workerScript, { headers: {'content-type':'application/javascript'}})
 );
});

Естьдва возможных отступления.ObjectURL, как указано выше, или, что более удобно, поместите real файл JavaScript в /my_workers/worker1.js

Преимущества этого подхода:

  1. SharedWorkers также могутподдерживаться.
  2. Вкладки могут совместно использовать одну кэшированную копию по фиксированному адресу.Подход BLOB-объектов увеличивает число случайных объектных URL для каждой вкладки.
36 голосов
/ 13 апреля 2012

Вы можете создать один файл JavaScript, который знает его контекст выполнения и может действовать как родительский сценарий и как рабочий. Давайте начнем с базовой структуры для файла, подобного этому:

(function(global) {
    var is_worker = !this.document;
    var script_path = is_worker ? null : (function() {
        // append random number and time to ID
        var id = (Math.random()+''+(+new Date)).substring(2);
        document.write('<script id="wts' + id + '"></script>');
        return document.getElementById('wts' + id).
            previousSibling.src;
    })();
    function msg_parent(e) {
        // event handler for parent -> worker messages
    }
    function msg_worker(e) {
        // event handler for worker -> parent messages
    }
    function new_worker() {
        var w = new Worker(script_path);
        w.addEventListener('message', msg_worker, false);
        return w;
    }
    if (is_worker)
        global.addEventListener('message', msg_parent, false);

    // put the rest of your library here
    // to spawn a worker, use new_worker()
})(this);

Как видите, скрипт содержит весь код как для точки зрения родителей, так и для работника, проверяя, является ли его собственный отдельный экземпляр рабочим с !document. Несколько громоздкое вычисление script_path используется для точного расчета пути сценария относительно родительской страницы, так как путь к new Worker относится к родительской странице, а не к сценарию.

26 голосов
/ 28 мая 2013

Используя метод Blob, как на счет рабочего завода:

var BuildWorker = function(foo){
   var str = foo.toString()
             .match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1];
   return  new Worker(window.URL.createObjectURL(
                      new Blob([str],{type:'text/javascript'})));
}

Так что вы можете использовать это так ...

var myWorker = BuildWorker(function(){
   //first line of worker
   self.onmessage(){....};
   //last line of worker
});

EDIT:

Я только что расширил эту идею, чтобы упростить взаимодействие между потоками: bridged-worker.js .

РЕДАКТИРОВАТЬ 2:

Приведенная выше ссылка относится к сущности, которую я создал. Кто-то позже превратил это в реальное репо .

11 голосов
/ 23 марта 2011

Веб-работники работают в совершенно разных контекстах, как отдельные программы.

Это означает, что код не может быть перемещен из одного контекста в другой в форме объекта, так как тогда они смогут ссылаться на объекты через замыкания, принадлежащие другому контексту.
Это особенно важно, поскольку ECMAScript разработан как однопоточный язык, и поскольку веб-работники работают в отдельных потоках, вы рискуете выполнить не поточнобезопасные операции.

Это снова означает, что веб-работникам необходимо инициализировать код в исходной форме.

Спецификация от WHATWG говорит

Если происхождение полученного абсолютный URL не совпадает с происхождение сценария ввода, затем бросить исключение SECURITY_ERR.

Таким образом, скрипты должны быть внешними файлами по той же схеме, что и оригинал страница: вы не можете загрузить скрипт из данные: URL или javascript: URL и https: страница не может начать работников использование сценариев с http: URL.

но, к сожалению, на самом деле это не объясняет, почему нельзя было разрешить передачу строки с исходным кодом конструктору.

6 голосов
/ 13 июня 2013

лучше читать для встроенного работника ..

    var worker_fn = function(e) 
    {
        self.postMessage('msg from worker');            
    };

    var blob = new Blob(["onmessage ="+worker_fn.toString()], { type: "text/javascript" });

    var worker = new Worker(window.URL.createObjectURL(blob));
    worker.onmessage = function(e) 
    {
       alert(e.data);
    };
    worker.postMessage("start"); 
5 голосов
/ 05 апреля 2018

Недавний ответ (2018)

Вы можете использовать Greenlet :

Переместить асинхронную функцию в ее собственный поток. Упрощенная однофункциональная версия Workerize .

Пример:

import greenlet from 'greenlet'

const getName = greenlet(async username => {
  const url = `https://api.github.com/users/${username}`
  const res = await fetch(url)
  const profile = await res.json()
  return profile.name
})

console.log(await getName('developit'))
4 голосов
/ 20 декабря 2013

Взяв ответ Adria и поместив его в копируемую функцию, которая работает с текущими Chrome и FF, но не с IE10 (работник из blob вызывает ошибку безопасности ).

var newWorker = function (funcObj) {
    // Build a worker from an anonymous function body
    var blobURL = URL.createObjectURL(new Blob(
        ['(', funcObj.toString(), ')()'],
        {type: 'application/javascript'}
     ));

    var worker = new Worker(blobURL);

    // Won't be needing this anymore
    URL.revokeObjectURL(blobURL);

    return worker;
}

А вот рабочий пример http://jsfiddle.net/ubershmekel/YYzvr/

2 голосов
/ 16 сентября 2013

Взгляните на плагин vkThread.С помощью плагина htis вы можете взять любую функцию в своем основном коде и выполнить ее в потоке (веб-работник).Таким образом, вам не нужно создавать специальный «файл веб-работника».

http://www.eslinstructor.net/vkthread/

- Вадим

2 голосов
/ 28 октября 2016

В зависимости от вашего варианта использования вы можете использовать что-то вроде

task.js Упрощенный интерфейс для выполнения кода, интенсивно использующего процессор, для запуска на всех ядрах (node.js и web)

Примером может быть

function blocking (exampleArgument) {
    // block thread
}

// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);

// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
    // do something with result
});
...