- PVSM.RU - https://www.pvsm.ru -

Скрещиваем WebWorker и Promise

Если уж есть необходимость скрещивать WebWorker с XMLHttpRequest [1], то пора бы скрестить его с любой функцией, а заодно разобраться с обещаниями ES6 [2].

Цель — научиться делать вот так:
new PromiseWorker(array => array.sort()).Invoke([3,2,1]).then(result => console.log(result));
(Здесь и далее используются arrow functions [3] для краткости)

Как известно, true-way для создания воркера — передать путь к файлу в качестве единственного параметра: new Worker("/JS/worker.js") . повесить обработчик onmessage, вызывать postmessage и придерживаться подобного стиля в файле воркера. По моему мнению на одну функцию слишком жирно создавать целый файл, да и возиться с событиями-обработчиками уже не комильфо. Хорошо, что есть Blob [4] и уже упомянутые Promises.

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

var FnToWorker = fn => {
   var workerBody = "self.addEventListener('message'," +
        "function (d) {" +
            "var result;" +
            "try {" +
                "result = (" + fn.toString() + ")(d.data.Data);" +
                "self.postMessage({ Result: result, Id: d.data.Id });" +
            "} catch (e) {" +
                "self.postMessage({ Error: e, Id: d.data.Id });" +
            "}" +
        "});"
    var worker = new Worker(URL.createObjectURL(new Blob([workerBody])));
    return worker;
}

Да, тут отвратительная конкатенация строк, fn.toString() и прочие ужасные вещи… главное что этот код можно один раз написать и забыть о нем.
Как указали в комментариях, данная конструкция накладывает некоторые ограничения: функция должна не иметь внешних зависимостей (переменные замыкания, неразрешенные [5] для воркеров объекты window), т.к. к ним не будет доступа из потока воркера. Функции, созданные с помощью системных функций (например, Function.prototype.bind), не могут быть использованы в воркерах из-за того, что fn.toString() не вернет тело функции.

Вот так будет выглядеть Invoke:

var promises = []; //Очередь вызовов
var Invoke = data => {
    var message = { Data: data, Id: performance.now() }; //У каждого сообщения Id, чтобы ничего не путалось
    var p = new Promise((resolve, reject) => {
        promises[message.Id] = { resolve: resolve, reject: reject };
    });
    worker.postMessage(message); //Запускаем воркер
    return p;
}

Использовать Promise не сложно: в конструктор передаем функцию от двух аргументов: resolve и reject. Это функции которые надо будет вызвать в случае успеха операции и неуспеха соответственно. В нашем случае они будут вызываться после того, как отработает воркер:

var OnMessage = data => {
    if (data.data.Error) {
        promises[data.data.Id].reject(data.data.Result);
    } else {
        promises[data.data.Id].resolve(data.data.Result);
    }
    promises[data.data.Id] = undefined;
}

Ну и вот так оно будет выглядеть в сборе: http://jsfiddle.net/sXJ4M/1/ [6]

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

Автор: fransua

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/58776

Ссылки в тексте:

[1] XMLHttpRequest: http://habrahabr.ru/post/218989/

[2] обещаниями ES6: http://www.html5rocks.com/en/tutorials/es6/promises/

[3] arrow functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/arrow_functions

[4] Blob: https://developer.mozilla.org/en-US/docs/Web/API/Blob

[5] разрешенные: https://developer.mozilla.org/en-US/docs/Web/Reference/Functions_and_classes_available_to_workers

[6] http://jsfiddle.net/sXJ4M/1/: http://jsfiddle.net/sXJ4M/1/

[7] Источник: http://habrahabr.ru/post/219037/