понедельник, 30 декабря 2013 г.

JavaScript Улучшенные Web Worker


Web-worker - это JavaScript-технология, позволяющая запускать изолированные участки кода в отдельном потоке. Код из web worker не тормозит работу графического интерфейса и выполняется быстрее, чем код на странице, что делает использование web worker очень привлекательным для ресурсоёмких расчётов вроде рисования графики или криптографии.

Обычно, если в web worker всего одна функция, то мы просто пишем:

- в коде страницы (снаружи):

var worker = new Worker("myscript.js");
worker.onmessage (event.data){workerСallback(event.data);}
function workerСallback(data){ /*do something with data object;*/}
worker.postMessage({some:"some"});

- в коде web worker (внутри):

onmessage = function (event) {postMessage(mySingleFunction(event.data));}

Однако, если в web worker добавить ещё одну функцию, вызываемую извне, то количество кода, обеспечивающего вызов этих функций возрастёт и он будет выглядеть уже не так изящно:

- в коде страницы (снаружи):

function firstFunctionСallback(data){   /*do something with data object;*/}

function secondFunctionСallback(data){ }

worker.onmessage (msg){
  if(msg.data.callback == "firstFunctionСallback"){
      firstFunctionСallback(msg.data.result);
  }
  if(msg.data.callback == "secondFunctionСallback"){
      firstFunctionСallback(msg.data.result);
  }
}

worker.postMessage({functionName: "firstFunction", data: data);


- в коде web worker (внутри):

onmessage = function (event) {
  var functionName = event.data.functionName;
  if(functionName == "firstFinction"){
      postMessage({callback: "firstFunctionСallback", result: firstFinction(event.data.data)});
  }
  if(functionName == "secondFunction"){
      postMessage({callback: "secondFunctionСallback", result: secondFunction(event.data.data)});
  }
  ...
}


При таком подходе нет возможности использовать в качестве коллбэков анонимные функции и при каждом добавлении новой функции в web worker нужно каждый раз снаружи и внутри web worker писать некоторое количество дополнительного кода, обеспечивающего вызов этой функции.

Чтобы избежать этого web worker можно обернуть в объект, который будет выполнять эту работу.

Назовём такой объект Performer и разместим его в коде страницы (снаружи):

function Performer(scriptSource) {
    var worker = new Worker(scriptSource), callbacks = {}, nextRequestId = 0;
    this.perform = function(functionName, params, callback) {
        callbacks["request_" + (++nextRequestId)] = callback;
        worker.postMessage(
         {functionName: functionName, params: params, requestId: nextRequestId}
        );
    }
    worker.onmessage = function(msg) {
        callbacks["request_" + msg.data.requestId](msg.data.result);
        delete callbacks["request_" + msg.data.requestId];
    }
}


Во внутреннем коде web worker изменим обработчик внешних сообщений:

onmessage = function (event) {    
  var requestId = event.data.requestId;
  var workerFunction = eval(event.data.functionName);
  var params = event.data.params;
  var result =  workerFunction(params);
  postMessage({result: result, requestId: requestId});
}


Теперь можно добавлять в web worker любые функции и вызывать их извне без написания вспомогательного кода, а также использовать анонимные функции в коллбэках:

var performer = new Performer("myscript.js");
performer.perform("firstFunction", {some: "some"}, function(result){console.log("result1="+result);});
performer.perform("secondFunction", {some: "some"}, function(result){console.log("result2="+result);});


Ели web worker размещён не в отдельном файле скрипта, а встроен в страницу,
то в коде перформера должны быть учтены кроссбраузерные различия.

С их учётом часть перформера, которая отвечает за инициализацию web worker, будет выглядеть так:

function Performer(scriptText) {
    var worker = null;  
    try {// Firefox
        var Url = window.webkitURL || window.URL;
        worker = new Worker(Url.createObjectURL(new Blob([ scriptText ])));
    } catch (browserNotSupportWindowUrl) {
        try {// Chrome
            worker = new Worker('data:application/javascript,' + encodeURIComponent(scriptText));
        } catch (browserNotSupportWorkers) {// Opera
            eval(scriptText);
            worker = {
                postMessage : function(data) {
                    var workerFunction = eval(data.functionName);
                    worker.onmessage({
                        data : {
                            result : workerFunction(data.params),
                            requestId : data.requestId
                        }
                    });
                }
            };
        }
    }
...
}


а создание так:

var performer = new Performer($('#myscript').text());

Таким образом, даже в браузерах, не поддерживающих web worker, код web worker всё равно будет выполняться, просто медленнее.

Комментариев нет:

Отправить комментарий