Веб-работники без отдельного файла Javascript?

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

new Worker('longrunning.js')

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

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

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

31 ответ

Решение

http://www.html5rocks.com/en/tutorials/workers/basics/

Что, если вы хотите создать рабочий сценарий на лету или создать автономную страницу без необходимости создания отдельных рабочих файлов? С помощью 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>

Решение html5rocks по встраиванию кода веб-работника в HTML довольно ужасно.
К тому же бланк экранированного JavaScript-as-a-string не лучше, не в последнюю очередь потому, что он усложняет рабочий процесс (компилятор 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, как описано выше, или, что более удобно, поместите настоящий файл JavaScript в /my_workers/worker1.js

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

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

Вы можете создать один файл 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 относится к родительской странице, а не к сценарию.

С использованием 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
});

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

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

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

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

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

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

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

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

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

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

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

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

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

Переместите асинхронную функцию в ее собственный поток. Упрощенная однофункциональная версия 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'))

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

    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"); 

Простая обещанная версия, Function#callAsWorker, который принимает thisArg и аргументы (так же, как call) и возвращает обещание:

Function.prototype.callAsWorker = function (...args) {
    return new Promise( (resolve, reject) => {
        const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(...e.data));`,
            blob = new Blob([code], { type: "text/javascript" }),
            worker = new Worker(window.URL.createObjectURL(blob));
        worker.onmessage = e => resolve(e.data);
        worker.onerror = e => reject(e.message);
        worker.postMessage(args);
    });
}

// Demo
function add(...nums) {
    return nums.reduce( (a,b) => a+b );
}
// Let the worker execute the above function, with the specified arguments
add.callAsWorker(null, 1, 2, 3).then(function (result) {
    console.log('result: ', result);
});

Возьмите ответ Адрии и поместите его в копируемую функцию, которая работает с текущими 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/

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

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
});

Так что я думаю, что у нас есть еще один классный вариант для этого, благодаря шаблонным литералам в ES6. Это позволяет нам обойтись без дополнительной рабочей функции (и ее странной области видимости) и просто написать код, предназначенный для рабочего, в виде многострочного текста, во многом как в случае, когда мы использовали для хранения текста, но без необходимости в документе или DOM сделать это в. Пример:

const workerScript = `
self.addEventListener('message', function(e) {
  var data = e.data;
  console.log('worker recieved: ',data);
  self.postMessage('worker added! :'+ addOne(data.value));
  self.close();//kills the worker
}, false);
`;

Вот суть этого подхода.

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

Единственный реальный недостаток этой конкретной версии заключается в том, что линтеры не смогут привязывать код работника службы (поскольку это всего лишь строка), что является преимуществом для "подхода с использованием отдельных рабочих функций".

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

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

--Vadim

@Trincot's кажется лучшим на данный момент. Тем не менее, возможно, мы можем развить его немного дальше. Итак, идея в том,

  1. Давайте не будем изменятьFunction.prototype.
  2. Получите обещанную/потоковую версию функции для многопотоковой операции.
  3. Убедитесь, что функция все еще может быть вызвана синхронно, если это необходимо.

Таким образом, мы определяемThreadableкласс сspawnметод. Как только мы сделаем нашу функцию членом этого класса, она станет потоковой :)

https://developer.mozilla.org/es/docs/Web/Guide/Performance/Using_web_workers

    // Syntax: asyncEval(code[, listener])

var asyncEval = (function () {

  var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");

  oParser.onmessage = function (oEvent) {
    if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
    delete aListeners[oEvent.data.id];
  };


  return function (sCode, fListener) {
    aListeners.push(fListener || null);
    oParser.postMessage({
      "id": aListeners.length - 1,
      "code": sCode
    });
  };

})();

Попробуйте использовать jThread. https://github.com/cheprasov/jThread

// You can use simple calling like this
jThread(
    function(arr){
        //... some code for Worker
        return arr;
    }
    ,function(arr){
        //... done code
    }
)( [1,2,3,4,5,6,7] ); // some params

Мне понравился ответ, который дал ifbamoq, но не смог прокомментировать из-за глупой политики переполнения стека. Поэтому я приведу пример, который показывает, как выполняется интенсивная работа, и как она не блокирует основной поток.

Все это без проблем с CORS с нулевым происхождением - если вы похожи на меня и любите дважды щелкать html-файлы и обрабатывать их как маленькие программы. :-)

Используйте мой крошечный плагин https://github.com/zevero/worker-create

var worker_url = Worker.createURL(function(e){
  self.postMessage('Example post from Worker'); //your code here
});
var worker = new Worker(worker_url);

Для реализации Node.js можно использовать следующую адаптацию ответа Тринкота .Обратите внимание еще раз, чтоFunction.prototype.callAsWorker()беретthisArgи аргументы, какFunction.prototype.call()и возвращает обещание.

      const { Worker } = require ( 'worker_threads' );

Function.prototype.callAsWorker = function ( ...args ) {
    return new Promise( ( resolve, reject ) => {

        const code = `
            const { parentPort, workerData } = require ( 'worker_threads' );
            parentPort.postMessage( ( ${this.toString()} ).call( ...workerData ) )
        `;
        const worker = new Worker( code, { eval: true, workerData: args } );
            
        worker.on('message', ( msg ) => { resolve( msg ), worker.terminate() } );
        worker.on('error', ( err ) => { reject( err ), worker.terminate() } );
        worker.on('exit', ( code ) => {
            if ( code !== 0 ) {
                reject( new Error( `Worker stopped with exit code ${code}.` ) );
            }
        });

    });
}

// Demo
function add( ...nums ) {
    return nums.reduce( ( a, b ) => a + b );
}

// Let the worker execute the above function, with the specified arguments
let result = await add.callAsWorker( null, 1, 2, 3 );
console.log( 'result: ', result );

Я обнаружил, что CodePen в настоящее время не выделяет синтаксис встроенным <script> теги, которые не являются type="text/javascript" (или которые не имеют атрибута типа).

Поэтому я разработал похожее, но немного другое решение, используя помеченные блоки с break, который является единственным способом освобождения под залог <script> тег без создания функции-оболочки (что не нужно).

<!DOCTYPE html>
<script id="worker1">
  worker: { // Labeled block wrapper

    if (typeof window === 'object') break worker; // Bail if we're not a 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>

Это всего лишь дополнение к вышеупомянутому - у меня есть хорошие шаблоны для тестирования веб-работников в jsFiddle. Вместо Blob он использует jsFiddles ?js апи:

function workerFN() {
  self.onmessage = function(e) {
    switch(e.data.name) {
      case "" : 
      break;
      default:
        console.error("Unknown message:", e.data.name);
    }
  }
}
// This is a trick to generate real worker script that is loaded from server
var url = "/echo/js/?js="+encodeURIComponent("("+workerFN.toString()+")()");
var worker = new Worker(url);
worker.addEventListener("message", function(e) {
  switch(e.data.name) {
    case "" : 
    break;
    default:
      console.error("Unknown message:", e.data.name);
  }
})

Доступны обычные веб-рабочие и общие рабочие шаблоны.

Однострочный для запуска функций в воркерах:

const FunctionalWorker = fn => new Worker(window.URL.createObjectURL(new Blob(["(" + fn.toString() + ")()"], {type: "text/javascript"})));

Пример использования:

let fn = FunctionalWorker(() => {
    self.postMessage("hi");
});
fn.onmessage = msg => {
    console.log(msg);
};

Здесь консоль:

var worker=new Worker(window.URL.createObjectURL(new Blob([function(){
  //Long-running work here
  postMessage('done');
}.toString().split('\n').slice(1,-1).join('\n')],{type:'text/javascript'})));

worker.addEventListener('message',function(event){
  console.log(event.data);
});

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

// create a Blob object with a worker code
var blob = new Blob(["onmessage = function(e) { postMessage('msg from worker'); }"]);

// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);

// create a Worker
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
  console.log(e.data);
};
worker.postMessage("Send some Data"); 

мой взгляд на это:

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

function createAsyncWorker(fn){
    
    // asyncworker=createAsyncWorker(function(){
    //     importScripts('my_otherscript.js');
    //     self.onmessage = function([arg1,arg2]) {
    //         self.postMessage('msg from worker');
    //     };
    // })
    // await asyncworker.postMessage('arg1','value')
    // await asyncworker.postMessage('arg1','value')
    // asyncworker.worker.terminate()
    
    var worker = BuildWorker(fn);

    function postMessage(...message){
        let external={}, promise= new Promise((resolve,reject)=>{external.resolve=resolve;external.reject=reject;})
        worker.onmessage = function(message){ external.resolve(message.data)};
        worker.postMessage(message); // Start the worker.
        return promise;
    }

    return {worker,postMessage};
}

пример использования:

      autoarima = createAsyncWorker(function(){
    importScripts("https://127.0.0.1:11000/arima.js")
    
    self.onmessage=(message)=>{
        let [action,arg1,arg2]=message.data
        if(action=='load')
        {
            ARIMAPromise.then(ARIMA1 => {
                ARIMA=ARIMA1
                autoarima = new ARIMA({ auto: true });
                //   const ts = Array(10).fill(0).map((_, i) => i + Math.random() / 5)
                //   const arima = new ARIMA({ p: 2, d: 1, q: 2, P: 0, D: 0, Q: 0, S: 0, verbose: false }).train(ts)
                //   const [pred, errors] = arima.predict(10)
                postMessage('ok')
            });
        }
        if(action=='fit')
        {
            autoarima.fit(arg1)
            postMessage('ok')
        }
        if(action=='predict')
        {
            postMessage(autoarima.predict(arg1,arg2)) 
        }
    };
})
autoarima.terminate=function(){  this.worker.terminate(); }
autoarima.load=async function(...args){return await this.postMessage('load',...args)}
autoarima.fit=async function(...args){return await this.postMessage('fit',...args)}
autoarima.predict=async function(...args){return await this.postMessage('predict',...args)}

await autoarima.load()
await autoarima.fit(b_values)
await autoarima.predict(1)

Вы можете использовать веб-работников в одном и том же JavaScript, используя встроенных веб-работников.

Следующая статья поможет вам лучше понять веб-работников, их ограничения и отладку.

Освоение в веб-мастерах

Вы можете поместить содержимое вашего файла worker.js внутри обратных галочек (что позволяет использовать многострочную строковую константу) и создать работника из большого двоичного объекта, например:

var workerScript = `
    self.onmessage = function(e) {
        self.postMessage('message from worker');
    };
    // rest of worker code goes here
`;

var worker =
    new Worker(createObjectURL(new Blob([workerScript], { type: "text/javascript" })));

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

Я использую такой код, вы можете определить свое сообщение как функцию, отличную от обычного текста, чтобы редактор мог выделить ваш код, и jshint работает.

const worker = createWorker();

createWorker() {
    const scriptContent = getWorkerScript();
    const blob = new Blob([
        scriptContent,
    ], {
        type: "text/javascipt"
    });
    const worker = new Worker(window.URL.createObjectURL(blob));
    return worker;
}

getWorkerScript() {
    const script = {
        onmessage: function (e) {
            console.log(e);
            let result = "Hello " + e.data
            postMessage(result);
        }
    };
    let content = "";
    for (let prop in script){
        content += `${prop}=${script[prop].toString()}`;
    }
    return content;
}

было несколько ответов, но вот еще одна встроенная версия.

примечание: аргумент "self" является чисто косметическим для целей линтинга, фактический рабочий код начинается после первой скобки, self как обычно

Да, это возможно, я сделал это, используя файлы Blob и передавая обратный вызов

Я покажу вам, что делает класс, который я написал, и как он управляет выполнением обратных вызовов в фоновом режиме.

Сначала вы создаете экземпляр GenericWebWorker с любыми данными, которые вы хотели бы передать обратному вызову, который будет выполняться в Web Worker, который включает в себя функции, которые вы хотите использовать, в этом случае число, дату и функцию с именем blocker

var worker = new GenericWebWorker(100, new Date(), blocker)

Эта блокирующая функция будет выполнять бесконечное время в течение n миллисекунд

function blocker (ms) {
    var now = new Date().getTime();
    while(true) {
        if (new Date().getTime() > now +ms)
            return;
    }   
}

а затем вы используете это так

worker.exec((num, date, fnBlocker) => {
    /*Everithing here does not block the main thread
      and this callback has access to the number, date and the blocker */
    fnBlocker(10000) //All of this run in backgrownd
    return num*10

}).then(d => console.log(d)) //Print 1000

Теперь пришло время увидеть магию в примере ниже

/*https://github.com/fercarvo/GenericWebWorker*/
class GenericWebWorker {
    constructor(...ags) {
        this.args = ags.map(a => (typeof a == 'function') ? {type:'fn', fn:a.toString()} : a)
    }

    async exec(cb) {
        var wk_string = this.worker.toString();
        wk_string = wk_string.substring(wk_string.indexOf('{') + 1, wk_string.lastIndexOf('}'));            
        var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
        var wk = new Worker(wk_link);

        wk.postMessage({ callback: cb.toString(), args: this.args });
 
        var resultado = await new Promise((next, error) => {
            wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
            wk.onerror = e => error(e.message);
        })

        wk.terminate(); window.URL.revokeObjectURL(wk_link);
        return resultado
    }

    async parallel(arr, cb) {
        var res = [...arr].map(it => new GenericWebWorker(it, ...this.args).exec(cb))
        var all = await Promise.all(res)
        return all
    }

    worker() {
        onmessage = async function (e) {
            try {                
                var cb = new Function(`return ${e.data.callback}`)();
                var args = e.data.args.map(p => (p.type == 'fn') ? new Function(`return ${p.fn}`)() : p);

                try {
                    var result = await cb.apply(this, args); //If it is a promise or async function
                    return postMessage(result)

                } catch (e) { throw new Error(`CallbackError: ${e}`) }
            } catch (e) { postMessage({error: e.message}) }
        }
    }
}


function blocker (ms) {
    var now = new Date().getTime();
    while(true) {
        if (new Date().getTime() > now +ms)
            return;
    }   
}

setInterval(()=> console.log("Not blocked " + Math.random()), 1000)

console.log("\n\nstarting blocking code in Worker\n\n")

var worker = new GenericWebWorker(100, new Date(), blocker)

worker.exec((num, date, fnBlocker) => {
    fnBlocker(7000) //All of this run in backgrownd
    return num*10    
})
.then(d => console.log(`\n\nEnd of blocking code: result ${d}\n\n`)) //Print 1000

Другое решение - просто обернуть Worker в функцию, а затем создать BLOB-объект, вызывающий эту функцию следующим образом:

     function workerCode() {
        self.onmessage = function (e) {
          console.log("Got message from parent", e.data);
        };
        setTimeout(() => {
          self.postMessage("Message From Worker");
        }, 2000);
      }

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

      // Note: window.webkitURL.createObjectURL() in Chrome 10+.
      let worker = new Worker(window.URL.createObjectURL(blob));
      worker.onmessage = function (e) {
        console.log("Received: " + e.data);
      };
      worker.postMessage("hello"); // Start the worker.
Другие вопросы по тегам