So erstellen Sie einen Web Worker aus einer Zeichenfolge

83

Wie kann ich einen Web-Worker aus einer Zeichenfolge erstellen (die über eine POST-Anforderung bereitgestellt wird)?

Eine Möglichkeit, die ich mir vorstellen kann, aber ich bin nicht sicher, wie ich sie implementieren soll, besteht darin, einen Daten-URI aus der Serverantwort zu erstellen und diesen an den Worker-Konstruktor zu übergeben, aber ich habe gehört, dass einige Browser dies nicht zulassen dies aufgrund der gleichen Ursprungspolitik.

MDN gibt die Unsicherheit über die Ursprungspolitik in Bezug auf Daten-URIs an :

Hinweis: Der als Parameter des Worker-Konstruktors übergebene URI muss der Richtlinie mit demselben Ursprung entsprechen. Derzeit gibt es unter den Anbietern von Browsern Meinungsverschiedenheiten darüber, ob Daten-URIs denselben Ursprungs sind oder nicht. Gecko 10.0 (Firefox 10.0 / Thunderbird 10.0) und höher erlauben Daten-URIs als gültiges Skript für Worker. Andere Browser sind möglicherweise anderer Meinung.

Hier ist auch ein Beitrag , der es auf dem whatwg diskutiert .

Bigblind
quelle
Ich frage mich, ob CORS ( w3.org/TR/cors ) helfen würde. HTMl5rocks verwendet eine starke "Muss" -Sprache, wenn es um die gleiche Herkunftsrichtlinie für Mitarbeiter geht ( html5rocks.com/en/tutorials/workers/basics ). Daher ist CORS hier möglicherweise keine große Hilfe. Hast du es trotzdem versucht?
Pavel Veller

Antworten:

144

Zusammenfassung

  • blob: für Chrome 8+, Firefox 6+, Safari 6.0+, Opera 15+
  • data:application/javascript für Opera 10.60 - 12
  • eval sonst (IE 10+)

URL.createObjectURL(<Blob blob>)kann verwendet werden, um einen Web-Worker aus einer Zeichenfolge zu erstellen. Der Blob kann mit der veraltetenBlobBuilder API oder dem Konstruktor erstellt werden .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');

Kompatibilität

Web Arbeiter sind in der folgenden Browsern unterstützt Quelle :

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

Die Unterstützung dieser Methode basiert auf der Unterstützung der BlobAPI und der URL.createObjectUrlMethode. BlobKompatibilität :

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

IE10 unterstützt MSBlobBuilderund URL.createObjectURL. Beim Versuch, einen Web Worker aus einer blob:-URL zu erstellen, wird jedoch ein SecurityError ausgelöst.

Opera 12 unterstützt keine URLAPI. Einige Benutzer haben möglicherweise eine gefälschte Version des URLObjekts, dank dieses Hack-Insbrowser.js .

Fallback 1: Daten-URI

Opera unterstützt Daten-URIs als Argument für den WorkerKonstruktor. Hinweis: Vergessen Sie nicht, Sonderzeichen (wie #und %) zu maskieren .

// 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 kann als Fallback für Safari (<6) und IE 10 verwendet werden.

// 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.
quelle
3
@BrianFreid Danke für deine Bearbeitung, aber sie wird nicht benötigt. Wenn Sie ein paar Zeilen weiter schauen, sehen Sie "IE10 unterstützt MSBlobBuilderund URL.createObjectURL. Wenn Sie jedoch versuchen, einen Web Worker aus einer blob:-URL zu erstellen, wird ein SecurityError ausgelöst.". Das Hinzufügen MSBlobBuilderhat also keine Auswirkung. Die einzige Option ist Fallback Nr. 2.
Rob W
Opera 12 definiert nicht mehr URL(und damit auch keine Eigenschaften), und der Blob-Konstruktor wird heutzutage gut genug unterstützt.
Gsnedders
2
Ich habe überprüft, dass dies zumindest in der Vorschau immer noch in IE11 geschieht.
Benjamin Gruenbaum
1
Werden dataURIs nur in Opera oder in allen anderen Browsern (außer IE) unterstützt?
Jayarjo
1
@jayarjo data:-URIs für Web Worker werden auch in Firefox unterstützt, jedoch nicht in Chrome oder Opera 15+. Die Leistung von evalist nicht relevant. Sie werden nicht Millionen von Web-Mitarbeitern pro Sekunde erstellen.
Rob W
12

Ich stimme der derzeit akzeptierten Antwort zu, aber häufig ist das Bearbeiten und Verwalten des Worker-Codes hektisch, da er in Form einer Zeichenfolge vorliegt.

Optional können wir also den folgenden Ansatz verwenden, bei dem wir den Worker als Funktion behalten und dann zu string-> blob verdecken können:

// 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.

Dies wird in IE11 + und FF und Chrome getestet

Chanakya Vadla
quelle
1
@SenJacob Da dies kein Community-Wiki-Beitrag ist, sollten Sie potenzielle Probleme dem Poster über einen Kommentar anstatt über eine Bearbeitung anzeigen.
Auf Wiedersehen StackExchange
@FrankerZ Entschuldigung. Ich musste dafür sorgen, dass es in IE11 mit den Änderungen funktioniert, die ich vorgenommen habe. @ ChanuSukarno Könnten Sie bitte überprüfen, ob die Änderungen in Revision 3 in Ordnung sind?
Sen Jacob
Zu Ihrer Information, der "Typ: 'application / javascript; charset = utf-8'" gehört zum Blob-Konstruktor, nicht zum Aufruf von createObjectURL.
Sora2455
Also ... Sie bauen eine Funktion außerhalb Ihres Arbeiters auf, nur um sie in Ihrem Texteditor besser zu lesen? Das ist lächerlich. Sie müssen diese Funktion ohne Grund in zwei Kontexten in den Speicher laden.
ADJenks
4

Ich habe mit den meisten Ihrer Ideen einen Ansatz gewählt und einige von meinen hinzugefügt. Das einzige, was mein Code für Worker benötigt, ist, "this" zu verwenden, um auf "self" zu verweisen. Ich bin mir ziemlich sicher, dass dies sehr verbesserungsfähig ist:

// 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
quelle
2

Gute Antwort - Ich habe heute an einem ähnlichen Problem gearbeitet, als ich versucht habe, Web Worker mit Fallback-Funktionen zu erstellen, wenn diese nicht verfügbar sind (dh Worker-Skript im Hauptthread ausführen). Da sich dieser Thread auf das Thema bezieht, dachte ich, ich würde hier meine Lösung anbieten:

    <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
quelle
1

Abhängig von Ihrem Anwendungsfall können Sie so etwas wie verwenden

task.js Vereinfachte Schnittstelle zum Ausführen von CPU-intensivem Code auf allen Kernen (node.js und Web)

Ein Beispiel wäre

// 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
quelle
1

Wenn Sie den Code von @ Chanu_Sukarno erweitern, können Sie einfach eine Worker-Funktion (oder einen String) an diese Funktion übergeben und sie wird in einem Web-Worker ausgeführt:

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

Hier ist ein Beispiel für die Verwendung:

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


In NodeJS , doWorkerTasksieht wie folgt aus :

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
quelle
1

Die akzeptierte Antwort ist aufgrund der Unterstützung der Abwärtskompatibilität etwas komplex, daher wollte ich dasselbe posten, aber vereinfacht. Versuchen Sie dies in Ihrer (modernen) Browserkonsole:

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
quelle
-1

Sie können reale Daten von der objectURL abrufen und nicht nur durch Ändern responseTypevon entweder "text"oder "arraybuffer".

Hier ist eine Hin- und Her-Konvertierung von text/javascriptzu blobzu objectURLzurück zu bloboder text/javascript.

Wenn Sie sich fragen, verwende ich es, um einen Web-Worker ohne externe Dateien zu generieren.
Sie können es verwenden, um binären Inhalt zurückzugeben, z. B. ein YouTube-Video;) (aus dem Attribut <video> -Tag-Ressource)

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)}'
*/

quelle
-1

Verwenden Sie mein kleines Plugin https://github.com/zevero/worker-create

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

Sie können ihm aber auch eine Funktion geben.

Zevero
quelle