Jak utworzyć pracownika sieci Web z ciągu znaków

84

Jak mogę użyć funkcji tworzenia pracownika sieci Web na podstawie ciągu (który jest dostarczany za pośrednictwem żądania POST)?

Jednym ze sposobów, które przychodzą mi do głowy, ale nie jestem pewien, jak to zaimplementować, jest utworzenie identyfikatora URI danych z odpowiedzi serwera i przekazanie go do konstruktora Worker, ale słyszałem, że niektóre przeglądarki nie pozwalają to z powodu tej samej polityki pochodzenia.

MDN stwierdza niepewność co do polityki pochodzenia w odniesieniu do identyfikatorów URI danych :

Uwaga: identyfikator URI przekazany jako parametr konstruktora procesu roboczego musi być zgodny z zasadami tego samego pochodzenia. Wśród sprzedawców przeglądarek istnieje obecnie różnica zdań co do tego, czy identyfikatory URI danych są tego samego pochodzenia, czy też nie; Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0) i nowsze zezwalają na identyfikatory URI danych jako poprawny skrypt dla pracowników. Inne przeglądarki mogą się z tym nie zgadzać.

Tutaj jest również post omawiający to na whatwg .

duży ślepy
źródło
Zastanawiam się, czy CORS ( w3.org/TR/cors ) by pomógł. HTMl5rocks używa silnego języka „obowiązkowego”, jeśli chodzi o politykę tego samego pochodzenia dla pracowników ( html5rocks.com/en/tutorials/workers/basics ), więc być może CORS nie jest tutaj zbyt pomocny. Czy próbowałeś tego chociaż?
Pavel Veller

Odpowiedzi:

146

Podsumowanie

  • blob: dla Chrome 8+, Firefox 6+, Safari 6.0+, Opera 15+
  • data:application/javascript dla Opery 10.60 - 12
  • eval inaczej (IE 10+)

URL.createObjectURL(<Blob blob>)może służyć do tworzenia pracownika sieci Web z łańcucha. Obiekt BLOB można utworzyć przy użyciu przestarzałegoBlobBuilder interfejsu API lub konstruktora .Blob

Demo: http://jsfiddle.net/uqcFM/49/

// URL.createObjectURL
window.URL = window.URL || window.webkitURL;

// "Server response", used in all examples
var response = "self.onmessage=function(e){postMessage('Worker: '+e.data);}";

var blob;
try {
    blob = new Blob([response], {type: 'application/javascript'});
} catch (e) { // Backwards-compatibility
    window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
    blob = new BlobBuilder();
    blob.append(response);
    blob = blob.getBlob();
}
var worker = new Worker(URL.createObjectURL(blob));

// Test, used in all examples:
worker.onmessage = function(e) {
    alert('Response: ' + e.data);
};
worker.postMessage('Test');

Zgodność

Pracownicy sieciowi są obsługiwani w następujących źródłach przeglądarek :

  • Chrome 3
  • Firefox 3.5
  • IE 10
  • Opera 10.60
  • Safari 4

Obsługa tej metody opiera się na wsparciu BlobAPI i URL.createObjectUrlmetody. Blobkompatybilność :

  • Chrome 8+ ( WebKitBlobBuilder), 20+ ( Blobkonstruktor)
  • Firefox 6+ ( MozBlobBuilder), 13+ ( Blobkonstruktor)
  • Safari 6+ ( Blobkonstruktor)

IE10 obsługuje MSBlobBuilderi URL.createObjectURL. Jednak próba utworzenia elementu blob:roboczego sieci Web z -URL generuje błąd SecurityError.

Opera 12 nie obsługuje URLAPI. Niektórzy użytkownicy mogą mieć fałszywą wersję URLobiektu, dzięki temu włamaniubrowser.js .

Powrót 1: identyfikator URI danych

Opera obsługuje identyfikatory URI danych jako argument dla Workerkonstruktora. Uwaga: nie zapomnij o ucieczce znaków specjalnych (takich jak #i %).

// response as defined in the first example
var worker = new Worker('data:application/javascript,' +
                        encodeURIComponent(response) );
// ... Test as defined in the first example

Demo: http://jsfiddle.net/uqcFM/37/

Fallback 2: Eval

eval może być używany jako rozwiązanie zastępcze dla Safari (<6) i IE 10.

// Worker-helper.js
self.onmessage = function(e) {
    self.onmessage = null; // Clean-up
    eval(e.data);
};
// Usage:
var worker = new Worker('Worker-helper.js');
// `response` as defined in the first example
worker.postMessage(response);
// .. Test as defined in the first example
Rob W
źródło
3
@BrianFreid Dzięki za zmianę, ale nie jest to potrzebne. Jeśli spojrzysz kilka wierszy dalej, zobaczysz „IE10 obsługuje MSBlobBuilderi URL.createObjectURL. Jednak próba utworzenia Web blob:Workera z -URL generuje SecurityError.”. Tak więc dodanie nie MSBlobBuilderprzyniesie żadnego efektu, jedyną opcją jest rozwiązanie zastępcze nr 2.
Rob W
Opera 12 już nie definiuje URL(i dlatego też nie definiuje żadnych właściwości), a konstruktor Blob jest obecnie wystarczająco dobrze obsługiwany.
gsnedders
2
Sprawdziłem, że to nadal dzieje się w IE11, przynajmniej w podglądzie.
Benjamin Gruenbaum
1
Czy dataURI jest obsługiwana tylko w przeglądarce Opera lub we wszystkich innych przeglądarkach (z wyjątkiem IE)?
jayarjo
1
@jayarjo data:-URIs for Web Workers są również obsługiwane w przeglądarce Firefox, ale nie w przeglądarce Chrome ani Opera 15+. Wydajność evalnie ma znaczenia, nie będziesz tworzyć milionów pracowników sieci Web na sekundę.
Rob W,
12

Zgadzam się z obecnie akceptowaną odpowiedzią, ale często edycja i zarządzanie kodem pracownika będzie gorączkowe, ponieważ ma postać ciągu.

Więc opcjonalnie możemy użyć poniższego podejścia, w którym możemy zachować proces roboczy jako funkcję, a następnie ukryć go w string-> blob:

// function to be your worker
function workerFunction() {
    var self = this;
    self.onmessage = function(e) {
        console.log('Received input: ', e.data); // message received from main thread
        self.postMessage("Response back to main thread");
    }
}


///////////////////////////////

var dataObj = '(' + workerFunction + ')();'; // here is the trick to convert the above fucntion to string
var blob = new Blob([dataObj.replace('"use strict";', '')]); // firefox adds "use strict"; to any function which might block worker execution so knock it off

var blobURL = (window.URL ? URL : webkitURL).createObjectURL(blob, {
    type: 'application/javascript; charset=utf-8'
});


var worker = new Worker(blobURL); // spawn new worker

worker.onmessage = function(e) {
    console.log('Worker said: ', e.data); // message received from worker
};
worker.postMessage("some input to worker"); // Send data to our worker.

Jest to testowane w IE11 +, FF i Chrome

Chanakya Vadla
źródło
1
@SenJacob Ponieważ nie jest to post na wiki społeczności, powinieneś przedstawić plakatowi potencjalne problemy za pomocą komentarza zamiast edycji.
Goodbye StackExchange
@FrankerZ Przepraszamy. Musiałem sprawić, by działał w IE11 ze zmianami, które wprowadziłem. @ ChanuSukarno Czy mógłbyś sprawdzić, czy zmiany w wersji 3 są prawidłowe ?
Sen Jacob
FYI, „type: 'application / javascript; charset = utf-8'” należy do konstruktora Blob, a nie do wywołania createObjectURL.
Sora2455
Więc ... Budujesz funkcję poza swoim pracownikiem, aby po prostu lepiej ją przeczytać w edytorze tekstu? To niedorzeczne. Musisz załadować tę funkcję do pamięci w dwóch kontekstach bez powodu.
ADJenks,
4

Zrobiłem podejście do większości twoich pomysłów i dodałem kilka moich. Jedyną rzeczą, jakiej potrzebuje mój kod od pracownika, jest użycie „this” do określenia zakresu „self”. Jestem prawie pewien, że można to poprawić:

// Sample code
var code = function() {
    this.onmessage = function(e) {
        this.postMessage('Worker: '+e.data);
        this.postMessage('Worker2: '+e.data);
    };
};

// New thread worker code
FakeWorkerCode = function(code, worker) {
    code.call(this);
    this.worker = worker;
}
FakeWorkerCode.prototype.postMessage = function(e) {
    this.worker.onmessage({data: e});
}
// Main thread worker side
FakeWorker = function(code) {
    this.code = new FakeWorkerCode(code, this);
}
FakeWorker.prototype.postMessage = function(e) {
    this.code.onmessage({data: e});
}

// Utilities for generating workers
Utils = {
    stringifyFunction: function(func) {
        // Stringify the code
        return '(' + func + ').call(self);';
    },
    generateWorker: function(code) {
        // URL.createObjectURL
        windowURL = window.URL || window.webkitURL;   
        var blob, worker;
        var stringified = Utils.stringifyFunction(code);
        try {
            blob = new Blob([stringified], {type: 'application/javascript'});
        } catch (e) { // Backwards-compatibility
            window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
            blob = new BlobBuilder();
            blob.append(stringified);
            blob = blob.getBlob();
        }

        if ("Worker" in window) {
            worker = new Worker(windowURL.createObjectURL(blob));
        } else {
            worker = new FakeWorker(code);
        }
        return worker;
    }
};

// Generate worker
var worker = Utils.generateWorker(code);
// Test, used in all examples:
worker.onmessage = function(e) {
    alert('Response: ' + e.data);
};
function runWorker() {
    worker.postMessage('working fine');
}

Demo: http://jsfiddle.net/8N6aR/

lukelalo
źródło
4

Przyjęta odpowiedź jest nieco złożona ze względu na obsługę wstecznej kompatybilności, więc chciałem opublikować to samo, ale uproszczone. Spróbuj tego w swojej (nowoczesnej) konsoli przeglądarki:

const code = "console.log('Hello from web worker!')"
const blob = new Blob([code], {type: 'application/javascript'})
const worker = new Worker(URL.createObjectURL(blob))
// See the output in your console.

trusktr
źródło
2

Dobra odpowiedź - pracowałem dzisiaj nad podobnym problemem, próbując stworzyć Web Workery z możliwościami rezerwowymi, gdy są one niedostępne (tj. Uruchom skrypt roboczy w głównym wątku). Ponieważ ten wątek dotyczy tematu, pomyślałem, że przedstawię tutaj swoje rozwiązanie:

    <script type="javascript/worker">
        //WORKER FUNCTIONS
        self.onmessage = function(event) {
            postMessage('Hello, ' + event.data.name + '!');
        }
    </script>

    <script type="text/javascript">

        function inlineWorker(parts, params, callback) {

            var URL = (window.URL || window.webkitURL);

            if (!URL && window.Worker) {

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

                worker.onmessage = function(event) {
                  callback(event.data);
                };

                worker.postMessage(params);

            } else {

                var postMessage = function(result) {
                  callback(result);
                };

                var self = {}; //'self' in scope of inlineWorker. 
                eval(parts); //Converts self.onmessage function string to function on self via nearest scope (previous line) - please email [email protected] if this could be tidier.
                self.onmessage({ 
                    data: params 
                });
            }
        }

        inlineWorker(
            document.querySelector('[type="javascript/worker"]').textContent, 
            {
                name: 'Chaps!!'
            },
            function(result) {
                document.body.innerHTML = result;
            }
        );

    </script>
</body>

Chris GW Green
źródło
1

W zależności od przypadku użycia możesz użyć czegoś takiego jak

task.js Uproszczony interfejs do pobierania kodu obciążającego procesor do uruchamiania na wszystkich rdzeniach (node.js i web)

Przykładem może być

// turn blocking pure function into a worker task
const functionFromPostRequest = task.wrap('function (exampleArgument) {}');

// run task on a autoscaling worker pool
functionFromPostRequest('exampleArgumentValue').then(result => {
    // do something with result
});
Chad Scira
źródło
1

Rozwijając kod @ Chanu_Sukarno, możesz po prostu przekazać funkcję roboczą (lub ciąg znaków) do tej funkcji, a ona wykona ją wewnątrz pracownika sieci:

async function doWorkerTask(workerFunction, input, buffers) {
  // Create worker
  let fnString = '(' + workerFunction.toString().replace('"use strict";', '') + ')();';
  let workerBlob = new Blob([fnString]);
  let workerBlobURL = window.URL.createObjectURL(workerBlob, { type: 'application/javascript; charset=utf-8' });
  let worker = new Worker(workerBlobURL);

  // Run worker
  return await new Promise(function(resolve, reject) {
    worker.onmessage = function(e) { resolve(e.data); };
    worker.postMessage(input, buffers);
  });
}

Oto przykład, jak go używać:

function myTask() {
  self.onmessage = function(e) {
    // do stuff with `e.data`, then:
    self.postMessage("my response");
    self.close();
  }
}
let output = await doWorkerTask(myTask, input, inputBuffers);
// now you can do something with `output` (which will be equal to "my response")


W nodejs , doWorkerTaskwygląda następująco:

async function doWorkerTask(workerFunction, input, buffers) {
  let Worker = require('webworker-threads').Worker;
  let worker = new Worker(workerFunction);

  // Run worker
  return await new Promise(function(resolve, reject) {
    worker.onmessage = function(e) { resolve(e.data); };
    worker.postMessage(input, buffers);
  });
}
user993683
źródło
-1

Możesz uzyskać rzeczywiste dane z objectURL, a nie tylko blob, zmieniając na responseTypealbo "text"lub "arraybuffer".

Oto konwersja w przód iw tył z text/javascriptna blobz objectURLpowrotem na bloblub text/javascript.

jeśli się zastanawiasz, używam go do generowania pracownika sieciowego bez zewnętrznych plików
, możesz go użyć do zwrócenia zawartości binarnej, na przykład wideo z YouTube;) (z atrybutu zasobu tagu <video>)

var blob = new Blob(['self.onmessage=function(e){postMessage(e)}'],{type: 'text/javascript'});   //->console: (object)   Blob {size: 42, type: "text/javascript", slice: function}

var obju = URL.createObjectURL(js_blob); //->console:  "blob:http%3A//stackoverflow.com/02e79c2b-025a-4293-be0f-f121dd57ccf7"

var xhr = new XMLHttpRequest();
xhr.open('GET', 'blob:http%3A//stackoverflow.com/02e79c2b-025a-4293-be0f-f121dd57ccf7', true);
xhr.responseType = 'text'; /* or "blob" */
xhr.onreadystatechange = function(){
  if(xhr.DONE !== xhr.readyState) return;

  console.log(xhr.response);
}
xhr.send();

/*
  responseType "blob" ->console: (object)   Blob {size: 42, type: "text/javascript", slice: function}
  responseType "text" ->console: (text)     'self.onmessage=function(e){postMessage(e)}'
*/

źródło
-1

Użyj mojej małej wtyczki https://github.com/zevero/worker-create

var worker_url = Worker.create("self.postMessage('Example post from Worker');");
var worker = new Worker(worker_url);

Ale możesz też nadać mu funkcję.

zevero
źródło