insertar - llamar archivo javascript desde html
¿Trabajadores web sin un archivo Javascript separado? (23)
Respuesta reciente (2018)
Puedes usar Greenlet :
Mueve una función asíncrona en su propio hilo. Una versión simplificada de una sola función de Workerize .
Ejemplo:
import greenlet from ''greenlet''
const getName = greenlet(async username => {
const url = `https://api.github.com/users/${username}`
const res = await fetch(url)
const profile = await res.json()
return profile.name
})
console.log(await getName(''developit''))
Por lo que puedo decir, los trabajadores de la web deben escribirse en un archivo JavaScript independiente y llamarse así:
new Worker(''longrunning.js'')
Estoy usando el compilador de cierre para combinar y minimizar todo el código fuente de mi JavaScript, y prefiero no tener que tener a mis trabajadores en archivos separados para su distribución. Hay alguna manera de hacer esto?
new Worker(function() {
//Long-running work here
});
Dado que las funciones de primera clase son tan cruciales para JavaScript, ¿por qué la forma estándar de realizar un trabajo en segundo plano tiene que cargar un archivo de JavaScript completo desde el servidor?
Así que creo que ahora tenemos otra opción genial para esto, gracias a los literales de plantilla en ES6. Eso nos permite prescindir de la función de trabajador adicional (y su extraño alcance) y simplemente escribir el código destinado al trabajador como texto de varias líneas, como en el caso en el que estábamos usando para almacenar texto, pero sin necesidad de un documento o DOM para hacer eso en. Ejemplo:
const workerScript = `
self.addEventListener(''message'', function(e) {
var data = e.data;
console.log(''worker recieved: '',data);
self.postMessage(''worker added! :''+ addOne(data.value));
self.close();//kills the worker
}, false);
`;
Aquí hay un resumen del resto de ese enfoque .
Tenga en cuenta que podemos incluir las dependencias de funciones adicionales que queramos en el trabajador simplemente agrupándolas en una matriz y ejecutando .toString en cada una de ellas para reducirlas también en cadenas (debería funcionar siempre que sean declaraciones de funciones) y luego simplemente anteponer eso a la cadena de script. De esa manera, no tenemos que importar guiones que ya hayamos incluido en el alcance del código que estamos escribiendo.
El único inconveniente real de esta versión en particular es que los linters no serán capaces de forrar el código del trabajador de servicio (ya que es solo una cadena), lo que es una ventaja para el "enfoque de función de trabajador separado".
Creo que la mejor manera de hacerlo es usar un objeto Blob, a continuación puede ver un ejemplo simple.
// create a Blob object with a worker code
var blob = new Blob(["onmessage = function(e) { postMessage(''msg from worker''); }"]);
// Obtain a blob URL reference to our worker ''file''.
var blobURL = window.URL.createObjectURL(blob);
// create a Worker
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
console.log(e.data);
};
worker.postMessage("Send some Data");
Dependiendo de tu caso de uso puedes usar algo como
task.js Interfaz simplificada para que el código de CPU intensivo se ejecute en todos los núcleos (node.js y web)
Un ejemplo seria
function blocking (exampleArgument) {
// block thread
}
// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);
// run task on a autoscaling worker pool
blockingAsync(''exampleArgumentValue'').then(result => {
// do something with result
});
Descubrí que CodePen actualmente no resalta en sintaxis las etiquetas <script>
línea que no son type="text/javascript"
(o que no tienen ningún atributo de tipo).
Así que ideé una solución similar pero ligeramente diferente utilizando bloques etiquetados con break
, que es la única forma en que puede salir de una etiqueta <script>
sin crear una función de envoltura (que no es necesaria).
<!DOCTYPE html>
<script id="worker1">
worker: { // Labeled block wrapper
if (typeof window === ''object'') break worker; // Bail if we''re not a Worker
self.onmessage = function(e) {
self.postMessage(''msg from worker'');
};
// Rest of your worker code goes here.
}
</script>
<script>
var blob = new Blob([
document.querySelector(''#worker1'').textContent
], { type: "text/javascript" })
// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
</script>
Echa un vistazo al plugin vkThread. Con el complemento htis, puede tomar cualquier función en su código principal y ejecutarla en un hilo (trabajador web). Por lo tanto, no es necesario crear un "archivo de trabajador web" especial.
http://www.eslinstructor.net/vkthread/
--Vadim
Esto es solo una adición a lo anterior. Tengo unas plantillas agradables para probar trabajadores web en jsFiddle. En lugar de Blob usa jsFiddles ?js
Js api:
function workerFN() {
self.onmessage = function(e) {
switch(e.data.name) {
case "" :
break;
default:
console.error("Unknown message:", e.data.name);
}
}
}
// This is a trick to generate real worker script that is loaded from server
var url = "/echo/js/?js="+encodeURIComponent("("+workerFN.toString()+")()");
var worker = new Worker(url);
worker.addEventListener("message", function(e) {
switch(e.data.name) {
case "" :
break;
default:
console.error("Unknown message:", e.data.name);
}
})
Las plantillas normales de trabajador web y trabajador compartido están disponibles.
La solución html5rocks de incrustar el código del trabajador web en HTML es bastante horrible.
Y un blob de JavaScript-as-a-string escapado no es mejor, entre otras cosas porque complica el flujo de trabajo (el compilador de cierre no puede funcionar con cadenas).
Personalmente, me gustan mucho los métodos de toString, ¡pero @dan-man ESA expresión regular!
Mi enfoque preferido:
// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL( new Blob([ ''('',
function(){
//Long-running work here
}.toString(),
'')()'' ], { type: ''application/javascript'' } ) ),
worker = new Worker( blobURL );
// Won''t be needing this anymore
URL.revokeObjectURL( blobURL );
El soporte es la intersección de estas tres tablas:
- http://caniuse.com/#feat=webworkers
- http://caniuse.com/#feat=blobbuilder
- http://caniuse.com/#feat=bloburls
Sin embargo, esto no funcionará para un SharedWorker , porque la URL debe ser una coincidencia exacta, incluso si el parámetro opcional ''nombre'' coincide. Para un SharedWorker, necesitará un archivo JavaScript separado.
Actualización 2015 - La singularidad de ServiceWorker llega
Ahora hay una manera aún más poderosa de resolver este problema. Una vez más, almacene el código del trabajador como una función (en lugar de una cadena estática) y conviértalo utilizando .toString (), luego inserte el código en CacheStorage bajo una URL estática de su elección.
// Post code from window to ServiceWorker...
navigator.serviceWorker.controller.postMessage(
[ ''/my_workers/worker1.js'', ''('' + workerFunction1.toString() + '')()'' ]
);
// Insert via ServiceWorker.onmessage. Or directly once window.caches is exposed
caches.open( ''myCache'' ).then( function( cache )
{
cache.put( ''/my_workers/worker1.js'',
new Response( workerScript, { headers: {''content-type'':''application/javascript''}})
);
});
Hay dos posibles caídas. ObjectURL como anteriormente, o más fácilmente, ponga un archivo JavaScript real en /my_workers/worker1.js
Las ventajas de este enfoque son:
- SharedWorkers también puede ser compatible.
- Las pestañas pueden compartir una sola copia en caché en una dirección fija. El enfoque de blob prolifera objectURLs aleatorios para cada pestaña.
Los trabajadores web operan en contextos completamente separados como programas individuales.
Esto significa que el código no se puede mover de un contexto a otro en forma de objeto, ya que podrían hacer referencia a objetos a través de cierres que pertenecen al otro contexto.
Esto es especialmente importante ya que ECMAScript está diseñado para ser un lenguaje de un solo hilo, y como los trabajadores web operan en hilos separados, entonces corre el riesgo de que se realicen operaciones no seguras para subprocesos.
De nuevo, esto significa que los trabajadores web deben inicializarse con código en formato fuente.
La especificación de WHATWG dice
Si el origen de la URL absoluta resultante no es el mismo que el origen del script de entrada, lance una excepción SECURITY_ERR.
Por lo tanto, las secuencias de comandos deben ser archivos externos con el mismo esquema que la página original: no puede cargar una secuencia de comandos de datos: URL o javascript: URL, y una página https: no puede iniciar a los trabajadores utilizando secuencias de comandos con http: URL.
pero desafortunadamente no explica realmente por qué uno no pudo haber permitido pasar una cadena con código fuente al constructor.
Puede colocar el contenido de su archivo worker.js dentro de backticks (lo que permite una constante de cadena multilínea) y crear el trabajador desde un blob como este:
var workerScript = `
self.onmessage = function(e) {
self.postMessage(''message from worker'');
};
// rest of worker code goes here
`;
var worker =
new Worker(createObjectURL(new Blob([workerScript], { type: "text/javascript" })));
Esto es útil si, por cualquier motivo, no desea tener etiquetas de script separadas para el trabajador.
Puede crear un único archivo JavaScript que sea consciente de su contexto de ejecución y pueda actuar como un script principal y un trabajador. Comencemos con una estructura básica para un archivo como este:
(function(global) {
var is_worker = !this.document;
var script_path = is_worker ? null : (function() {
// append random number and time to ID
var id = (Math.random()+''''+(+new Date)).substring(2);
document.write(''<script id="wts'' + id + ''"></script>'');
return document.getElementById(''wts'' + id).
previousSibling.src;
})();
function msg_parent(e) {
// event handler for parent -> worker messages
}
function msg_worker(e) {
// event handler for worker -> parent messages
}
function new_worker() {
var w = new Worker(script_path);
w.addEventListener(''message'', msg_worker, false);
return w;
}
if (is_worker)
global.addEventListener(''message'', msg_parent, false);
// put the rest of your library here
// to spawn a worker, use new_worker()
})(this);
Como puede ver, la secuencia de comandos contiene todo el código para el punto de vista de los padres y del trabajador, verificando si su propia instancia individual es un trabajador con el !document
El cálculo de la ruta de acceso al script, algo difícil de script_path
se utiliza para calcular con precisión la ruta del script en relación con la página principal, ya que la ruta suministrada al new Worker
es relativa a la página principal, no al script.
Puede utilizar los trabajadores web en el mismo archivo JavaScript utilizando los trabajadores web en línea.
El artículo a continuación se dirigirá a usted para comprender fácilmente los trabajadores web y sus limitaciones y la depuración de los trabajadores web.
Tomar la respuesta de Adria y ponerla en una función de copia que se pueda pegar que funcione con Chrome y FF actuales pero no con IE10 (el trabajo de blob provoca un error de seguridad ).
var newWorker = function (funcObj) {
// Build a worker from an anonymous function body
var blobURL = URL.createObjectURL(new Blob(
[''('', funcObj.toString(), '')()''],
{type: ''application/javascript''}
));
var worker = new Worker(blobURL);
// Won''t be needing this anymore
URL.revokeObjectURL(blobURL);
return worker;
}
Y aquí hay un ejemplo de trabajo http://jsfiddle.net/ubershmekel/YYzvr/
Trate de usar jThread. https://github.com/cheprasov/jThread
// You can use simple calling like this
jThread(
function(arr){
//... some code for Worker
return arr;
}
,function(arr){
//... done code
}
)( [1,2,3,4,5,6,7] ); // some params
Una versión promisificada simple, Function#callAsWorker
, que toma un thisArg y argumentos (al igual que call
), y devuelve una promesa:
Function.prototype.callAsWorker = function (...args) {
return new Promise( (resolve, reject) => {
const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(...e.data));`,
blob = new Blob([code], { type: "text/javascript" }),
worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = e => resolve(e.data);
worker.onerror = e => reject(e.message);
worker.postMessage(args);
});
}
// Demo
function add(...nums) {
return nums.reduce( (a,b) => a+b );
}
// Let the worker execute the above function, with the specified arguments
add.callAsWorker(null, 1, 2, 3).then(function (result) {
console.log(''result: '', result);
});
Usando el método Blob
, ¿qué tal esto para una fábrica de trabajadores?
var BuildWorker = function(foo){
var str = foo.toString()
.match(/^/s*function/s*/(/s*/)/s*/{(([/s/S](?!/}$))*[/s/S])/)[1];
return new Worker(window.URL.createObjectURL(
new Blob([str],{type:''text/javascript''})));
}
Así que podrías usarlo así ...
var myWorker = BuildWorker(function(){
//first line of worker
self.onmessage(){....};
//last line of worker
});
EDITAR:
Acabo de ampliar esta idea para facilitar la comunicación entre hilos: bridged-worker.js .
EDIT 2:
El enlace anterior es a una esencia que he creado. Alguien más tarde lo convirtió en un repo real .
Utilice mi pequeño complemento https://github.com/zevero/worker-create
var worker_url = Worker.createURL(function(e){
self.postMessage(''Example post from Worker''); //your code here
});
var worker = new Worker(worker_url);
Utilizo código como este, puede definir su mensaje en línea como una función que no sea texto sin formato, por lo que el editor puede resaltar su código y jshint funciona.
const worker = createWorker();
createWorker() {
const scriptContent = getWorkerScript();
const blob = new Blob([
scriptContent,
], {
type: "text/javascipt"
});
const worker = new Worker(window.URL.createObjectURL(blob));
return worker;
}
getWorkerScript() {
const script = {
onmessage: function (e) {
console.log(e);
let result = "Hello " + e.data
postMessage(result);
}
};
let content = "";
for (let prop in script){
content += `${prop}=${script[prop].toString()}`;
}
return content;
}
aquí consola:
var worker=new Worker(window.URL.createObjectURL(new Blob([function(){
//Long-running work here
postMessage(''done'');
}.toString().split(''/n'').slice(1,-1).join(''/n'')],{type:''text/javascript''})));
worker.addEventListener(''message'',function(event){
console.log(event.data);
});
una mejor manera de leer para un trabajador en línea ..
var worker_fn = function(e)
{
self.postMessage(''msg from worker'');
};
var blob = new Blob(["onmessage ="+worker_fn.toString()], { type: "text/javascript" });
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e)
{
alert(e.data);
};
worker.postMessage("start");
http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers
¿Qué sucede si desea crear su script de trabajador sobre la marcha o crear una página independiente sin tener que crear archivos de trabajador separados? Con Blob (), puede "insertar" a su trabajador en el mismo archivo HTML que su lógica principal creando un identificador de URL para el código de trabajador como una cadena
Ejemplo completo de trabajador en línea BLOB:
<!DOCTYPE html>
<script id="worker1" type="javascript/worker">
// This script won''t be parsed by JS engines because its type is javascript/worker.
self.onmessage = function(e) {
self.postMessage(''msg from worker'');
};
// Rest of your worker code goes here.
</script>
<script>
var blob = new Blob([
document.querySelector(''#worker1'').textContent
], { type: "text/javascript" })
// Note: window.webkitURL.createObjectURL() in Chrome 10+.
var worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = function(e) {
console.log("Received: " + e.data);
}
worker.postMessage("hello"); // Start the worker.
</script>
https://developer.mozilla.org/es/docs/Web/Guide/Performance/Using_web_workers
// Syntax: asyncEval(code[, listener])
var asyncEval = (function () {
var aListeners = [], oParser = new Worker("data:text/javascript;charset=US-ASCII,onmessage%20%3D%20function%20%28oEvent%29%20%7B%0A%09postMessage%28%7B%0A%09%09%22id%22%3A%20oEvent.data.id%2C%0A%09%09%22evaluated%22%3A%20eval%28oEvent.data.code%29%0A%09%7D%29%3B%0A%7D");
oParser.onmessage = function (oEvent) {
if (aListeners[oEvent.data.id]) { aListeners[oEvent.data.id](oEvent.data.evaluated); }
delete aListeners[oEvent.data.id];
};
return function (sCode, fListener) {
aListeners.push(fListener || null);
oParser.postMessage({
"id": aListeners.length - 1,
"code": sCode
});
};
})();
Sí, es posible, lo hice usando archivos Blob y pasando una devolución de llamada
Le mostraré lo que hace una clase que escribí y cómo gestiona la ejecución de devoluciones de llamada en segundo plano.
Primero, cree una instancia de GenericWebWorker
con los datos que le gustaría pasar a la devolución de llamada que se ejecutará en Web Worker
, que incluye las funciones que desea usar, en este caso, un número, una fecha y una función denominada blocker
var worker = new GenericWebWorker(100, new Date(), blocker)
Esta función de bloqueo se ejecutará infinitamente durante n milisegundos.
function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}
y luego lo usas asi
worker.exec((num, date, fnBlocker) => {
/*Everithing here does not block the main thread
and this callback has access to the number, date and the blocker */
fnBlocker(10000) //All of this run in backgrownd
return num*10
}).then(d => console.log(d)) //Print 1000
Ahora, es hora de ver la magia en el siguiente ejemplo.
/*https://github.com/fercarvo/GenericWebWorker*/
class GenericWebWorker {
constructor(...ags) {
this.args = ags.map(a => (typeof a == ''function'') ? {type:''fn'', fn:a.toString()} : a)
}
async exec(cb) {
var wk_string = this.worker.toString();
wk_string = wk_string.substring(wk_string.indexOf(''{'') + 1, wk_string.lastIndexOf(''}''));
var wk_link = window.URL.createObjectURL( new Blob([ wk_string ]) );
var wk = new Worker(wk_link);
wk.postMessage({ callback: cb.toString(), args: this.args });
var resultado = await new Promise((next, error) => {
wk.onmessage = e => (e.data && e.data.error) ? error(e.data.error) : next(e.data);
wk.onerror = e => error(e.message);
})
wk.terminate(); window.URL.revokeObjectURL(wk_link);
return resultado
}
async parallel(arr, cb) {
var res = [...arr].map(it => new GenericWebWorker(it, ...this.args).exec(cb))
var all = await Promise.all(res)
return all
}
worker() {
onmessage = async function (e) {
try {
var cb = new Function(`return ${e.data.callback}`)();
var args = e.data.args.map(p => (p.type == ''fn'') ? new Function(`return ${p.fn}`)() : p);
try {
var result = await cb.apply(this, args); //If it is a promise or async function
return postMessage(result)
} catch (e) { throw new Error(`CallbackError: ${e}`) }
} catch (e) { postMessage({error: e.message}) }
}
}
}
function blocker (ms) {
var now = new Date().getTime();
while(true) {
if (new Date().getTime() > now +ms)
return;
}
}
setInterval(()=> console.log("Not blocked " + Math.random()), 1000)
console.log("/n/nstarting blocking code in Worker/n/n")
var worker = new GenericWebWorker(100, new Date(), blocker)
worker.exec((num, date, fnBlocker) => {
fnBlocker(7000) //All of this run in backgrownd
return num*10
})
.then(d => console.log(`/n/nEnd of blocking code: result ${d}/n/n`)) //Print 1000