web-dev-qa-db-fra.com

Comment créer un Web Worker à partir d'une chaîne

Comment puis-je utiliser créer un travailleur Web à partir d'une chaîne (qui est fournie via une demande POST))?

Une façon de penser, mais je ne sais pas comment l'implémenter, est de créer un URI de données à partir de la réponse du serveur et de le transmettre au constructeur Worker, mais j'ai entendu dire que certains navigateurs ne permettent pas cela, en raison de la même politique d'origine.

MDN indique l'incertitude concernant la politique d'origine concernant les URI de données :

Remarque: L'URI transmis en tant que paramètre du constructeur Worker doit obéir à la même politique d'origine. Il existe actuellement un désaccord entre les fournisseurs de navigateurs quant à savoir si les URI de données sont de même origine ou non; Gecko 10.0 (Firefox 10.0/Thunderbird 10.0) et versions ultérieures autorisent les URI de données en tant que script valide pour les travailleurs. D'autres navigateurs peuvent ne pas être d'accord.

Voici également un article en discuter sur le whatwg .

75
bigblind

Sommaire

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

URL.createObjectURL(<Blob blob>) peut être utilisé pour créer un travailleur Web à partir d'une chaîne. Le blob peut être créé à l'aide de l'API BlobBuilder obsolète ou le constructeur Blob .

Démo: 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');

Compatibilité

Les travailleurs Web sont pris en charge dans les navigateurs suivants source:

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

La prise en charge de cette méthode est basée sur la prise en charge de l'API Blob et de la méthode URL.createObjectUrl. Blob compatibilité :

  • Chrome 8+ (WebKitBlobBuilder), 20+ (Blob constructeur)
  • Firefox 6+ (MozBlobBuilder), 13+ (Blob constructeur)
  • Safari 6+ (constructeur [Blob)

IE10 prend en charge MSBlobBuilder et URL.createObjectURL. Cependant, essayer de créer un Web Worker à partir d'une blob: - URL lève une SecurityError.

Opera 12 ne prend pas en charge l'API URL. Certains utilisateurs peuvent avoir une fausse version de l'objet URL, grâce à ce hack dans browser.js .

Fallback 1: data-URI

Opera prend en charge les URI de données comme argument du constructeur Worker. Remarque: N'oubliez pas de échapper les caractères spéciaux (comme # Et %).

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

Démo: http://jsfiddle.net/uqcFM/37/

Fallback 2: Eval

eval peut être utilisé comme solution de rechange pour Safari (<6) et 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
130
Rob W

Je suis d'accord avec la réponse actuellement acceptée, mais souvent, la modification et la gestion du code du travailleur seront très complexes sous la forme d'une chaîne.

Donc, nous pouvons éventuellement utiliser l'approche ci-dessous où nous pouvons garder le travailleur en tant que fonction, puis convertir en 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.

Ceci est testé dans IE11 + et FF et Chrome

7
Chanakya Vadla

J'ai fait une approche avec la plupart de vos idées et en ajoutant certaines des miennes. La seule chose dont mon code a besoin sur le travailleur est d'utiliser "ceci" pour désigner la portée "soi". Je suis sûr que c'est très améliorable:

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

Démo: http://jsfiddle.net/8N6aR/

3
lukelalo

En développant le code de @ Chanu_Sukarno, vous pouvez simplement passer une fonction de travail (ou chaîne) à cette fonction et elle l'exécutera dans un travailleur Web:

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

Voici un exemple d'utilisation:

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


Dans nodejs, doWorkerTask ressemble à ceci:

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

Bonne réponse - J'ai travaillé sur un problème similaire aujourd'hui en essayant de créer des Web Workers avec des capacités de secours lorsqu'ils ne sont pas disponibles (c'est-à-dire exécuter le script de travail dans le thread principal). Comme ce fil se rapporte au sujet, j'ai pensé apporter ma solution ici:

    <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>
1
Chris GW Green

Selon votre cas d'utilisation, vous pouvez utiliser quelque chose comme

task.js Interface simplifiée pour faire fonctionner du code gourmand en CPU sur tous les cœurs (node.js et web)

Un exemple serait

// 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
});
1
Chad Scira

Vous pouvez obtenir des données réelles à partir de objectURL et pas simplement blob en changeant le responseType en _ "text" ou "arraybuffer".

Voici une conversion de va-et-vient de text/javascript à blob à objectURL retour à blob ou text/javascript.

si vous vous posez la question, je l'utilise pour générer un web-travailleur sans fichiers externes
vous pouvez l'utiliser pour renvoyer du contenu binaire, par exemple une vidéo YouTube;) (à partir de l'attribut de ressource de balise <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)}'
*/
0
user257319