programacion - llamar javascript desde html
¿Cómo hacer código javascript sin bloqueo? (4)
¿Cómo puedo hacer una llamada de función de JavaScript simple y sin bloque? Por ejemplo:
//begin the program
console.log(''begin'');
nonBlockingIncrement(10000000);
console.log(''do more stuff'');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n){
var i=0;
while(i<n){
i++;
}
console.log(''0 incremented to ''+i);
}
salidas
"beginPage"
"0 incremented to 10000000"
"do more stuff"
¿Cómo puedo formar este bucle simple para ejecutar de forma asíncrona y enviar los resultados a través de una función de devolución de llamada? La idea es no bloquear "hacer más cosas":
"beginPage"
"do more stuff"
"0 incremented to 10000000"
Intenté seguir tutoriales sobre devoluciones de llamada y continuaciones, pero todos parecen depender de bibliotecas o funciones externas. Ninguno de ellos responde la pregunta en el vacío: ¿cómo se escribe el código Javascript para que no sea bloqueador?
He buscado mucho esta respuesta antes de preguntar; por favor no asumas que no miré. Todo lo que encontré es específico de Node.js ( [1] , [2] , [3] , [4] , [5] ) o específico de otras funciones o bibliotecas ( [6] , [7] , [8] , [9] , [10] , [11] ), notablemente JQuery y setTimeout()
. Ayúdeme a escribir código que no sea de bloqueo mediante Javascript , no herramientas escritas por Javascript como JQuery y Node. Amablemente vuelva a leer la pregunta antes de marcarla como duplicada.
En general, hay dos maneras de hacerlo, que yo sepa. Una es usar setTimeout
(o requestAnimationFrame
si está haciendo esto en un entorno de soporte). @Alnitak mostró cómo hacer esto en otra respuesta. Otra forma es utilizar un trabajador web para finalizar su lógica de bloqueo en un hilo separado, de modo que el hilo principal de la IU no esté bloqueado.
Usando requestAnimationFrame
o setTimeout
:
//begin the program
console.log(''begin'');
nonBlockingIncrement(100, function (currentI, done) {
if (done) {
console.log(''0 incremented to '' + currentI);
}
});
console.log(''do more stuff'');
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n, callback){
var i = 0;
function loop () {
if (i < n) {
i++;
callback(i, false);
(window.requestAnimationFrame || window.setTimeout)(loop);
}
else {
callback(i, true);
}
}
loop();
}
Usando el trabajador web:
/***** Your worker.js *****/
this.addEventListener(''message'', function (e) {
var i = 0;
while (i < e.data.target) {
i++;
}
this.postMessage({
done: true,
currentI: i,
caller: e.data.caller
});
});
/***** Your main program *****/
//begin the program
console.log(''begin'');
nonBlockingIncrement(100, function (currentI, done) {
if (done) {
console.log(''0 incremented to '' + currentI);
}
});
console.log(''do more stuff'');
// Create web worker and callback register
var worker = new Worker(''./worker.js''),
callbacks = {};
worker.addEventListener(''message'', function (e) {
callbacks[e.data.caller](e.data.currentI, e.data.done);
});
//define the slow function; this would normally be a server call
function nonBlockingIncrement(n, callback){
const caller = ''nonBlockingIncrement'';
callbacks[caller] = callback;
worker.postMessage({
target: n,
caller: caller
});
}
No puede ejecutar la solución de trabajador web ya que requiere un archivo worker.js
separado para hospedar la lógica del trabajador.
No puede ejecutar Dos bucles al mismo tiempo, recuerde que JS es un solo hilo.
Entonces, hacer esto nunca funcionará
function loopTest() {
var test = 0
for (var i; i<=100000000000, i++) {
test +=1
}
return test
}
setTimeout(()=>{
//This will block everything, so the second won''t start until this loop ends
console.log(loopTest())
}, 1)
setTimeout(()=>{
console.log(loopTest())
}, 1)
Si desea obtener múltiples hilos, debe utilizar Web Workers , pero tienen que tener un archivo js separado y solo puede pasarles objetos.
Pero, he logrado usar Web Workers sin archivos separados generando archivos Blob y puedo pasarles las funciones de devolución de llamada también.
//A fileless Web Worker
class ChildProcess {
//@param {any} ags, Any kind of arguments that will be used in the callback, functions too
constructor(...ags) {
this.args = ags.map(a => (typeof a == ''function'') ? {type:''fn'', fn:a.toString()} : a)
}
//@param {function} cb, To be executed, the params must be the same number of passed in the constructor
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
}
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}) }
}
}
}
setInterval(()=>{console.log(''Not blocked code '' + Math.random())}, 1000)
console.log("starting blocking synchronous code in Worker")
console.time("/nblocked");
var proc = new ChildProcess(blockCpu, 43434234);
proc.exec(function(block, num) {
//This will block for 10 sec, but
block(10000) //This blockCpu function is defined below
return `/n/nbla bla ${num}/n` //Captured in the resolved promise
}).then(function (result){
console.timeEnd("/nblocked")
console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })
//random blocking function
function blockCpu(ms) {
var now = new Date().getTime();
var result = 0
while(true) {
result += Math.random() * Math.random();
if (new Date().getTime() > now +ms)
return;
}
}
Para que su ciclo no sea bloqueante, debe dividirlo en secciones y permitir que el ciclo de procesamiento de eventos JS consuma eventos del usuario antes de continuar con la siguiente sección.
La manera más fácil de lograr esto es hacer una cierta cantidad de trabajo, y luego usar setTimeout(..., 0)
para poner en cola la siguiente parte del trabajo. Crucialmente, esa cola permite que el bucle de eventos JS procese cualquier evento que haya sido puesto en cola mientras tanto, antes de pasar al siguiente trabajo:
function yieldingLoop(count, chunksize, callback, finished) {
var i = 0;
(function chunk() {
var end = Math.min(i + chunksize, count);
for ( ; i < end; ++i) {
callback.call(null, i);
}
if (i < count) {
setTimeout(chunk, 0);
} else {
finished.call(null);
}
})();
}
con uso:
yieldingLoop(1000000, 1000, function(i) {
// use i here
}, function() {
// loop done here
});
Consulte http://jsfiddle.net/alnitak/x3bwjjo6/ para obtener una demostración donde la función de callback
simplemente establece una variable para el recuento de iteraciones actual, y un setTimeout
basado en setTimeout
diferente sondea el valor actual de esa variable y actualiza la página con su valor .
SetTimeout con devoluciones de llamada es el camino a seguir. Sin embargo, entienda que los ámbitos de su función no son los mismos que en C # u otro entorno de subprocesos múltiples.
Javascript no espera a que finalice la devolución de llamada de su función.
Si usted dice:
function doThisThing(theseArgs) {
setTimeout(function (theseArgs) { doThatOtherThing(theseArgs); }, 1000);
alert(''hello world'');
}
Su alerta se disparará antes de que la función que pasó lo haga.
La diferencia es que esa alerta bloqueó el hilo, pero su devolución de llamada no.