¿Cómo puedo crear una función asincrónica en Javascript?

143

Mira este código :

<a href="#" id="link">Link</a>
<span>Moving</span>

$('#link').click(function () {
    console.log("Enter");
    $('#link').animate({ width: 200 }, 2000, function() {
         console.log("finished");            
    });    
    console.log("Exit");    
});

Como puede ver en la consola, la función "animar" es asíncrona y "bifurca" el flujo del código de bloqueo del controlador de eventos. De hecho :

$('#link').click(function () {
    console.log("Enter");
    asyncFunct();
    console.log("Exit");    
});

function asyncFunct() {
    console.log("finished");
}

siga el flujo del código de bloque!

Si deseo crear mi function asyncFunct() { }con este comportamiento, ¿cómo puedo hacerlo con javascript / jquery? Creo que hay una estrategia sin el uso de setTimeout()

markzzz
fuente
eche un vistazo a las fuentes jQuery :)
yatskevich
El método .animate () utiliza una devolución de llamada. Animate llamará a la devolución de llamada cuando se complete la animación. Si necesita el mismo comportamiento de .animate (), lo que necesita es una devolución de llamada (llamada por la función "principal" después de algunas otras operaciones). Es diferente si necesita una función asincrónica "completa" (una función llamada sin bloquear el flujo de ejecución). En este caso, podría usar setTimeout () con un retraso cercano a 0.
Fabio Buda
@Fabio Buda: ¿por qué callback () debería implementar una especie de asíncrono? De hecho, no jsfiddle.net/5H9XT/9
markzzz 01 de
de hecho, después de la "devolución de llamada", cité un método asincrónico "completo" con setTimeout. Me refería a la devolución de llamada como pseudoasync en la forma en que se llama a la función después de otro código :-)
Fabio Buda

Respuestas:

185

No puede hacer una función asincrónica verdaderamente personalizada. Eventualmente tendrá que aprovechar una tecnología proporcionada de forma nativa, como:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Algunas API HTML5, como la API de archivo, la API de base de datos web
  • Tecnologías que soportan onload
  • ... muchos otros

De hecho, para la animación que utiliza jQuery setInterval.

pimvdb
fuente
1
¡Estaba discutiendo esto con un amigo ayer, así que esta respuesta es perfecta! Entiendo y puedo identificar las funciones asíncronas y usarlas en JS correctamente. Pero simplemente no sé por qué no podemos implementar los personalizados. Es como una caja negra que sabemos cómo hacer que funcione (usando, por ejemplo, setInterval) pero que ni siquiera podemos abrirla para ver cómo se hace. ¿Tienes más información sobre el tema?
Matheus Felipe
2
@MatheusFelipe esas funciones son nativas de la implementación del motor de JavaScript y lo único en lo que puede confiar es en las especificaciones, por ejemplo , temporizadores HTML5 y confiar en la naturaleza de caja negra que se comportan de acuerdo con las especificaciones.
Spoike
10
@MatheusFelipe youtu.be/8aGhZQkoFbQ mejor charla hasta ahora sobre este tema ...
Andreas Niedermair
Algunas implementaciones, en particular Node.js, admiten setImmediate
Jon Surrell
¿qué pasa promises. ¿Le da un awaitable?
Nithin Chandran
69

Puedes usar un temporizador:

setTimeout( yourFn, 0 );

(dónde yourFn hay una referencia a su función)

o con Lodash :

_.defer( yourFn );

Difiere la invocación funchasta que la pila de llamadas actual se haya borrado. Cualquier argumento adicional se proporciona funccuando se invoca.

Šime Vidas
fuente
3
Esto no funciona, mi función de JavaScript que se dibuja en un lienzo sigue haciendo que la interfaz de usuario no responda.
gab06
2
@ gab06: diría que la función de dibujo del lienzo se está bloqueando por sus propios motivos. Divide su acción en muchas más pequeñas e invoca cada una de ellas con un temporizador: verás que la interfaz de esta manera responde a los clics de tu mouse, etc.
Marco Faustinelli
El enlace está roto.
JulianSoto
1
@JulianSoto solucionado
Šime Vidas
1
El tiempo mínimo setTimeoutes de 4 milisegundos según la especificación HTML5. Darle 0 todavía tomará ese tiempo mínimo. Pero sí, funciona bien como diferidor de funciones.
hegez
30

aquí tienes una solución simple (otros escriben sobre ello) http://www.benlesh.com/2012/05/calling-javascript-function.html

Y aquí tienes la solución lista para arriba:

function async(your_function, callback) {
    setTimeout(function() {
        your_function();
        if (callback) {callback();}
    }, 0);
}

PRUEBA 1 ( puede generar '1 x 2 3' o '1 2 x 3' o '1 2 3 x' ):

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

PRUEBA 2 ( siempre generará 'x 1' ):

async(function() {console.log('x');}, function() {console.log(1);});

Esta función se ejecuta con el tiempo de espera 0: simulará una tarea asincrónica

fider
fuente
66
La PRUEBA 1 en realidad solo puede emitir '1 2 3 x' y la PRUEBA 2 garantiza la salida '1 x' cada vez. La razón de los resultados inesperados en TEST 2 es porque console.log(1)se llama y la salida ( undefined) se pasa como el segundo argumento a async(). En el caso de TEST 1, creo que no entiendes completamente la cola de ejecución de JavaScript. Debido a que cada una de las llamadas se realizará console.log()en la misma pila, xse garantiza que se registrará en último lugar. Votaría esta respuesta por información errónea, pero no tengo suficiente reputación.
Joshua Piccari
1
@Joshua: Parece @fider destinado a prueba de escritura 2 como: async(function() {console.log('x')}, function(){console.log(1)});.
nzn
Sí @nzn y @Joshua quise decir TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- ya lo
corregí
La salida de TEST 2 es 1 x en asíncrono (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen
10

Aquí hay una función que toma otra función y genera una versión que se ejecuta de forma asíncrona.

var async = function (func) {
  return function () {
    var args = arguments;
    setTimeout(function () {
      func.apply(this, args);
    }, 0);
  };
};

Se utiliza como una forma simple de hacer una función asíncrona:

var anyncFunction = async(function (callback) {
    doSomething();
    callback();
});

Esto es diferente de la respuesta de @ fider porque la función en sí misma tiene su propia estructura (no se agregó ninguna devolución de llamada, ya está en la función) y también porque crea una nueva función que se puede usar.

Ethan McTague
fuente
setTimeout no se puede usar en un bucle (llame a la misma función varias veces con argumentos distintos) .
user2284570
@ user2284570 Para eso están los cierres. (function(a){ asyncFunction(a); })(a)
Giratorio
1
IIRC, también podría lograr esto sin un cierre:setTimeout(asyncFunction, 0, a);
Giratorio
1
Si por asíncrono queremos decir: ejecutar en segundo plano, paralelo al hilo principal, entonces esto no es realmente asíncrono. Todo lo que hará es retrasar la ejecución del proceso.nextTick. Cualquier código que tenga en la función se ejecutará en el hilo principal. Si la función se configuró para calcular PI, la aplicación se congelará, con o sin tiempo de espera.
Mike M
1
No entiendo por qué esta respuesta está votada. Cuando pongo esto en mi código, el programa se bloquea hasta que finaliza la función, que es exactamente lo que no debe hacer.
Martin Argerami
6

Editar: entendí totalmente mal la pregunta. En el navegador, lo usaría setTimeout. Si fuera importante que se ejecutara en otro hilo, usaría Web Workers .

Linus Thiel
fuente
1
? Esto no hace una función asíncrona: O
markzzz
6

Tarde, pero para mostrar una solución fácil de usar promisesdespués de su introducción en ES6 , maneja las llamadas asincrónicas mucho más fácil:

Establece el código asincrónico en una nueva promesa:

var asyncFunct = new Promise(function(resolve, reject) {
    $('#link').animate({ width: 200 }, 2000, function() {
        console.log("finished");                
        resolve();
    });             
});

Nota para configurar resolve()cuando finalice la llamada asincrónica.
Luego agrega el código que desea ejecutar después de que finalice la llamada asincrónica dentro .then()de la promesa:

asyncFunct.then((result) => {
    console.log("Exit");    
});

Aquí hay un fragmento de ello:

$('#link').click(function () {
    console.log("Enter");
    var asyncFunct = new Promise(function(resolve, reject) {
        $('#link').animate({ width: 200 }, 2000, function() {
            console.log("finished");            	
            resolve();
        }); 			
    });
    asyncFunct.then((result) => {
        console.log("Exit");    
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>

o JSFiddle

hd84335
fuente
6

Esta página guía a través de los conceptos básicos para crear una función de JavaScript asíncrono.

Desde ES2017, las funciones asincrónicas de JavaScript son mucho más fáciles de escribir. También debería leer más sobre Promesas .

shanabus
fuente
El enlace está muerto.
Martin Argerami
3

Si desea utilizar los parámetros y regular el número máximo de funciones asíncronas, puede utilizar un trabajador asíncrono simple que he creado:

var BackgroundWorker = function(maxTasks) {
    this.maxTasks = maxTasks || 100;
    this.runningTasks = 0;
    this.taskQueue = [];
};

/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
    var self = this;
    if(self.runningTasks >= self.maxTasks) {
        self.taskQueue.push({ task: task, delay: delay, params: params});
    } else {
        self.runningTasks += 1;
        var runnable = function(params) {
            try {
                task(params);
            } catch(err) {
                console.log(err);
            }
            self.taskCompleted();
        }
        // this approach uses current standards:
        setTimeout(runnable, delay, params);
    }
}

BackgroundWorker.prototype.taskCompleted = function() {
    this.runningTasks -= 1;

    // are any tasks waiting in queue?
    if(this.taskQueue.length > 0) {
        // it seems so! let's run it x)
        var taskInfo = this.taskQueue.splice(0, 1)[0];
        this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
    }
}

Puedes usarlo así:

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
João Alves
fuente
2
Function.prototype.applyAsync = function(params, cb){
      var function_context = this;
      setTimeout(function(){
          var val = function_context.apply(undefined, params); 
          if(cb) cb(val);
      }, 0);
}

// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);

Aunque no es fundamentalmente diferente a las otras soluciones, creo que mi solución hace algunas cosas buenas adicionales:

  • permite parámetros para las funciones
  • pasa la salida de la función a la devolución de llamada
  • se agrega para Function.prototypepermitir una mejor manera de llamarlo

Además, la similitud con la función incorporada Function.prototype.applyme parece apropiada.

Benjamin Kuykendall
fuente
1

Junto a la gran respuesta de @pimvdb, y en caso de que te lo preguntes, async.js tampoco ofrece funciones realmente asincrónicas. Aquí hay una versión (muy) simplificada del método principal de la biblioteca:

function asyncify(func) { // signature: func(array)
    return function (array, callback) {
        var result;
        try {
            result = func.apply(this, array);
        } catch (e) {
            return callback(e);
        }
        /* code ommited in case func returns a promise */
        callback(null, result);
    };
}

Por lo tanto, la función protege contra errores y la entrega con gracia a la devolución de llamada para que la maneje, pero el código es tan sincrónico como cualquier otra función JS.

Mike M
fuente
1

Desafortunadamente, JavaScript no proporciona una funcionalidad asíncrona. Funciona solo en un solo hilo. Pero la mayoría de los navegadores modernos ofrecen Workers , que son segundos scripts que se ejecutan en segundo plano y pueden devolver un resultado. Entonces, llegué a una solución que creo que es útil ejecutar asincrónicamente una función, que crea un trabajador para cada llamada asincrónica.

El siguiente código contiene la función async para llamar en segundo plano.

Function.prototype.async = function(callback) {
    let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
    let worker = new Worker(window.URL.createObjectURL(blob));
    worker.addEventListener("message", function(e) {
        this(e.data.result);
    }.bind(callback), false);
    return function() {
        this.postMessage(Array.from(arguments));
    }.bind(worker);
};

Este es un ejemplo de uso:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

Esto ejecuta una función que itera un for con un número enorme para tomar tiempo como demostración de asincronía, y luego obtiene el doble del número pasado. El asyncmétodo proporciona un functionque llama a la función deseada en segundo plano, y en lo que se proporciona como parámetro de asyncdevoluciones de llamada returnen su parámetro único. Entonces, en la función de devolución de llamada, alertel resultado.

Davide Cannizzo
fuente
0

MDN tiene un buen ejemplo sobre el uso de setTimeout preservando "esto".

Como el siguiente:

function doSomething() {
    // use 'this' to handle the selected element here
}

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
xtian
fuente