Come posso creare una funzione asincrona in Javascript?


143

Dai un'occhiata a questo codice :

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

Come puoi vedere nella console, la funzione "animate" è asincrona e "fork" è il flusso del codice di blocco del gestore eventi. Infatti :

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

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

seguire il flusso del codice blocco!

Se desidero creare il mio function asyncFunct() { }con questo comportamento, come posso farlo con javascript / jquery? Penso che ci sia una strategia senza l'uso di setTimeout()


dai un'occhiata alle fonti jQuery :)
yatskevich

Il mathod .animate () usa un callback. Animate chiamerà il callback quando l'animazione è completa. Se hai bisogno dello stesso comportamento di .animate () quello di cui hai bisogno è un callback (chiamato dalla funzione "main" dopo alcune altre operazioni). È diverso se hai bisogno di una funzione asincrona "completa" (una funzione chiamata senza bloccare il flusso di esecuzione). In questo caso è possibile utilizzare setTimeout () con un ritardo quasi 0.
Fabio Buda,

@Fabio Buda: perché callback () dovrebbe implementare una sorta di asincrono? In effetti, non funziona jsfiddle.net/5H9XT/9
markzzz

infatti dopo "callback" ho citato un metodo asincrono "completo" con setTimeout. Intendevo callback come pseudo-asincrono nel modo in cui la funzione viene chiamata dopo un altro codice :-)
Fabio Buda

Risposte:


185

Non è possibile creare una funzione asincrona veramente personalizzata. Alla fine dovrai sfruttare una tecnologia fornita in modo nativo, come:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Alcune API HTML5 come l'API di file, l'API del database Web
  • Tecnologie che supportano onload
  • ... molti altri

In effetti, per l'animazione utilizza jQuery setInterval.


1
Ne ho discusso ieri con un amico, quindi questa risposta è perfetta! Capisco e posso identificare le funzioni asincrone e usarle correttamente in JS. Ma semplicemente perché non possiamo implementare quelli personalizzati non mi è chiaro. È come una scatola nera che sappiamo come farlo funzionare (usando, diciamo, setInterval) ma che non possiamo nemmeno aprirlo per vedere come è fatto. Ti capita di avere maggiori informazioni sull'argomento?
Matheus Felipe,

2
@MatheusFelipe quelle funzioni sono native dell'implementazione del motore javascript e l'unica cosa su cui puoi fare affidamento sono le specifiche, ad esempio i timer HTML5 e la fiducia nella natura della scatola nera che si comportano secondo le specifiche.
Spoike,

10
@MatheusFelipe youtu.be/8aGhZQkoFbQ è il miglior discorso finora su questo argomento ...
Andreas Niedermair,

Alcuni implemantations, in particolare Node.js, supportosetImmediate
Jon Surrell

che dire promises. Dà un awaitable?
Nithin Chandran,

69

Puoi usare un timer:

setTimeout( yourFn, 0 );

(dove yourFnè un riferimento alla tua funzione)

oppure, con Lodash :

_.defer( yourFn );

Rimanda a invocare funcfino a quando lo stack di chiamate corrente non è stato cancellato. Eventuali argomenti aggiuntivi vengono forniti funcquando viene invocato.


3
Questo non funziona, la mia funzione JavaScript che disegna in una tela continua a rendere l'interfaccia utente non risponde.
gab06,

2
@ gab06 - Direi che la tua funzione di disegno su tela si sta bloccando per i suoi buoni motivi. Dividi la sua azione in molte più piccole e invoca ognuna di esse con un timer: vedrai che l'interfaccia in questo modo risponde ai clic del mouse, ecc.
Marco Faustinelli,

Il collegamento è interrotto.
JulianSoto,

1
@JulianSoto risolto
Šime Vidas

1
Il tempo minimo per setTimeoutè di 4 millisecondi secondo la specifica HTML5. Darlo 0 richiederà comunque quel minimo di tempo. Ma sì, funziona bene come differitore di funzione.
Hegez,

30

qui hai una soluzione semplice (altri ne scrivono) http://www.benlesh.com/2012/05/calling-javascript-function.html

E qui hai la soluzione sopra pronta:

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

TEST 1 ( può produrre '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);

TEST 2 ( emetterà sempre 'x 1' ):

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

Questa funzione viene eseguita con timeout 0: simulerà l'attività asincrona


6
TEST 1 può effettivamente generare sempre '1 2 3 x' e TEST 2 garantisce sempre '1 x'. Il motivo dei risultati imprevisti in TEST 2 è perché console.log(1)viene chiamato e output ( undefined) viene passato come secondo argomento a async(). Nel caso di TEST 1, penso che tu non capisca completamente la coda di esecuzione di JavaScript. Poiché ciascuna delle chiamate console.log()avviene nello stesso stack, xè garantito che venga registrato per ultimo. Voterei questa risposta per disinformazione, ma non ho abbastanza rappresentante.
Joshua Piccari,

1
@Joshua: Sembra @fider scopo di test di scrittura 2 come: async(function() {console.log('x')}, function(){console.log(1)});.
nzn

Sì, @nzn e @Joshua intendevo TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- l'ho corretto già
fider l'

L'output di TEST 2 è 1 x in asincrono (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen,

10

Ecco una funzione che accetta un'altra funzione e genera una versione che esegue asincrona.

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

È usato come un modo semplice per fare una funzione asincrona:

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

Questo è diverso dalla risposta di @fider perché la funzione stessa ha una propria struttura (nessun callback aggiunto, è già nella funzione) e anche perché crea una nuova funzione che può essere utilizzata.


setTimeout non può essere utilizzato in un ciclo (chiamare più volte la stessa funzione con argomenti distinti) .
user2284570,

@ user2284570 Ecco a cosa servono le chiusure. (function(a){ asyncFunction(a); })(a)
Girevole il

1
IIRC, potresti anche raggiungere questo obiettivo senza una chiusura:setTimeout(asyncFunction, 0, a);
Girevole il

1
Se per asincrono intendiamo: in esecuzione in background, parallelo al thread principale, questo non è veramente asincrono. Tutto ciò che farà è ritardare l'esecuzione su process.nextTick. Qualunque codice tu abbia nella funzione verrà eseguito sul thread principale. Se la funzione è stata impostata per calcolare PI, l'app si bloccherà, con o senza timeout!
Mike M,

1
Non capisco perché questa risposta sia stata votata. Quando ho messo nel mio codice, i blocchi di programma finché la funzione è finito, che esattamente quello che dovrebbe non fare.
Martin Argerami,

6

Modifica: ho completamente frainteso la domanda. Nel browser, vorrei usare setTimeout. Se fosse importante che funzionasse in un altro thread, avrei usato Web Workers .


1
? Questo non ha una funzione asincrona: O
markzzz

6

In ritardo, ma per mostrare una soluzione semplice utilizzando promisesdopo la loro introduzione in ES6 , gestisce le chiamate asincrone molto più facilmente:

Hai impostato il codice asincrono in una nuova promessa:

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

Nota per impostare resolve()al termine della chiamata asincrona.
Quindi aggiungi il codice che desideri eseguire al termine della chiamata asincrona all'interno .then()della promessa:

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

Ecco un frammento di esso:

$('#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


6

Questa pagina illustra le basi della creazione di una funzione javascript asincrona.

Da ES2017, le funzioni asincrone javacript sono molto più facili da scrivere. Dovresti anche leggere di più su Promesse .


Il link è morto.
Martin Argerami,

3

Se si desidera utilizzare i parametri e regolare il numero massimo di funzioni asincrone, è possibile utilizzare un semplice operatore asincrono che ho creato:

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

Puoi usarlo in questo modo:

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

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);

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

Anche se non sostanzialmente diverso dalle altre soluzioni, penso che la mia soluzione faccia alcune altre cose carine:

  • consente parametri alle funzioni
  • passa l'output della funzione al callback
  • viene aggiunto per Function.prototypeconsentire un modo migliore per chiamarlo

Inoltre, la somiglianza con la funzione integrata Function.prototype.applymi sembra appropriata.


1

Accanto alla grande risposta di @pimvdb, e nel caso in cui ti chiedessi, async.js non offre nemmeno funzioni veramente asincrone. Ecco una versione (molto) ridotta del metodo principale della libreria:

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

Quindi la funzione protegge dagli errori e lo gestisce con garbo al callback da gestire, ma il codice è sincrono come qualsiasi altra funzione JS.


1

Sfortunatamente, JavaScript non fornisce una funzionalità asincrona. Funziona solo in un singolo thread. Ma la maggior parte dei browser moderni fornisce Workers , che sono secondi script che vengono eseguiti in background e possono restituire un risultato. Quindi, ho raggiunto una soluzione che ritengo utile eseguire in modo asincrono una funzione, che crea un lavoratore per ogni chiamata asincrona.

Il codice seguente contiene la funzione async da chiamare in background.

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

Questo è un esempio per l'uso:

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

Questo esegue una funzione che esegue un'iterazione forcon un numero enorme per richiedere tempo come dimostrazione di asincronicità, quindi ottiene il doppio del numero passato. Il asyncmetodo fornisce uno functionche chiama la funzione desiderata in background e in quello che viene fornito come parametro di asynccallback returnnel suo parametro univoco. Quindi nella funzione di richiamata ho alertil risultato.


0

MDN ha un buon esempio sull'uso di setTimeout preservando "questo".

Come il seguente:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.