Comment créer une fonction asynchrone en Javascript?

143

Découvrez ce code :

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

Comme vous pouvez le voir dans la console, la fonction "animer" est asynchrone, et elle "dérive" le flux du code de bloc du gestionnaire d'événements. En réalité :

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

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

suivez le flux du code de bloc!

Si je souhaite créer mon function asyncFunct() { }avec ce comportement, comment puis-je le faire avec javascript / jquery? Je pense qu'il ya une stratégie sans l'utilisation de setTimeout()

Markzzz
la source
jetez un oeil aux sources jQuery :)
yatskevich
La méthode .animate () utilise un rappel. Animate appellera le rappel lorsque l'animation sera terminée. Si vous avez besoin du même comportement de .animate (), vous avez besoin d'un callback (appelé par la fonction "main" après quelques autres opérations). C'est différent si vous avez besoin d'une fonction asynchrone "complète" (une fonction appelée sans bloquer le flux d'exécution). Dans ce cas, vous pouvez utiliser setTimeout () avec un délai proche de 0.
Fabio Buda
@Fabio Buda: pourquoi callback () devrait implémenter une sorte d'async? En fait, ce n'est pas le cas jsfiddle.net/5H9XT/9
markzzz
en fait après "callback" j'ai cité une méthode asynchrone "complète" avec setTimeout. Je voulais dire rappel comme pseudo-asynchrone dans la façon dont la fonction est appelée après un autre code :-)
Fabio Buda

Réponses:

185

Vous ne pouvez pas créer une fonction asynchrone vraiment personnalisée. Vous devrez éventuellement tirer parti d'une technologie fournie de manière native, telle que:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Certaines API HTML5 telles que l'API de fichier, l'API de base de données Web
  • Des technologies qui soutiennent onload
  • ... beaucoup d'autres

En fait, pour l'animation que jQuery utilise setInterval .

pimvdb
la source
1
J'en discutais avec un ami hier alors cette réponse est parfaite! Je comprends et je peux identifier les fonctions asynchrones et les utiliser correctement dans JS. Mais simplement pourquoi ne pouvons-nous pas mettre en œuvre des modèles personnalisés ne me semble pas clair. C'est comme une boîte noire que nous savons comment le faire fonctionner (en utilisant, par exemple setInterval) mais que nous ne pouvons même pas l'ouvrir pour voir comment cela se fait. Avez-vous plus d'informations sur le sujet?
Matheus Felipe
2
@MatheusFelipe ces fonctions sont natives de l'implémentation du moteur javascript et la seule chose sur laquelle vous pouvez compter est les spécifications, par exemple les minuteries HTML5 et faites confiance à la nature de la boîte noire qu'elles se comportent selon les spécifications.
Spoike
10
@MatheusFelipe youtu.be/8aGhZQkoFbQ meilleure discussion à ce jour sur ce sujet ...
Andreas Niedermair
Certaines implémentations, en particulier Node.js, supportentsetImmediate
Jon Surrell
qu'en est-il promises. Cela donne-t-il un awaitable?
Nithin Chandran le
69

Vous pouvez utiliser une minuterie:

setTimeout( yourFn, 0 );

(où yourFnest une référence à votre fonction)

ou, avec Lodash :

_.defer( yourFn );

Diffère l'appel de funcjusqu'à ce que la pile d'appels actuelle soit effacée. Tous les arguments supplémentaires sont fournis funclors de son invocation.

Šime Vidas
la source
3
Cela ne fonctionne pas, ma fonction javascript qui dessine dans un canevas continue de faire en sorte que l'interface utilisateur ne réponde pas.
gab06 du
2
@ gab06 - Je dirais que votre fonction de dessin sur toile se bloque pour ses propres bonnes raisons. Divisez son action en plusieurs plus petites et invoquez chacune d'elles avec un minuteur: vous verrez que l'interface de cette façon répond à vos clics de souris, etc.
Marco Faustinelli
Le lien est rompu.
JulianSoto
1
@JulianSoto corrigé
Šime Vidas
1
Le temps minimum pour setTimeoutest de 4 millisecondes par spécification HTML5. Lui donner 0 prendra toujours ce laps de temps minimum. Mais oui, cela fonctionne bien comme un report de fonction.
hegez le
30

ici vous avez une solution simple (autre écrire à ce sujet) http://www.benlesh.com/2012/05/calling-javascript-function.html

Et ici, vous avez ci-dessus la solution prête:

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

TEST 1 ( peut produire '1 x 2 3' ou '1 2 x 3' ou '1 2 3 x' ):

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

TEST 2 ( affichera toujours 'x 1' ):

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

Cette fonction est exécutée avec timeout 0 - elle simulera une tâche asynchrone

fider
la source
6
TEST 1 ne peut en fait sortir que «1 2 3 x» et TEST 2 est garanti de produire «1 x» à chaque fois. Les résultats inattendus de TEST 2 s'expliquent par le fait qu'il console.log(1)est appelé et que la sortie ( undefined) est transmise comme deuxième argument à async(). Dans le cas du TEST 1, je pense que vous ne comprenez pas entièrement la file d'attente d'exécution de JavaScript. Parce que chacun des appels se console.log()produisant dans la même pile, xest garanti pour être enregistré en dernier. Je voterais contre cette réponse pour désinformation, mais je n'ai pas assez de représentants.
Joshua Piccari
1
@Joshua: Il semble @fider voulu écrire TEST 2 comme: async(function() {console.log('x')}, function(){console.log(1)});.
nzn le
Oui @nzn et @Joshua je voulais dire TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- je l'ai déjà corrigé
fider
La sortie du TEST 2 est 1 x en async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen le
10

Voici une fonction qui prend une autre fonction et génère une version qui s'exécute de manière asynchrone.

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

Il est utilisé comme un moyen simple de créer une fonction asynchrone:

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

C'est différent de la réponse de @ fider parce que la fonction elle-même a sa propre structure (aucun rappel ajouté, elle est déjà dans la fonction) et aussi parce qu'elle crée une nouvelle fonction qui peut être utilisée.

Ethan McTague
la source
setTimeout ne peut pas être utilisé dans une boucle (appelez plusieurs fois la même fonction avec des arguments distincts) .
user2284570
@ user2284570 C'est à cela que servent les fermetures. (function(a){ asyncFunction(a); })(a)
Pivotant
1
IIRC, vous pouvez également y parvenir sans fermeture:setTimeout(asyncFunction, 0, a);
Pivot du
1
Si par async nous entendons: s'exécuter en arrière-plan, parallèlement au thread principal, alors ce n'est pas vraiment asynchrone. Tout cela ne fera que retarder l'exécution du process.nextTick. Le code que vous avez dans la fonction sera exécuté sur le thread principal. Si la fonction a été définie pour calculer PI, l'application se fige, avec ou sans délai!
Mike M
1
Je ne comprends pas pourquoi cette réponse est votée pour. Lorsque je mets cela dans mon code, le programme se bloque jusqu'à ce que la fonction soit terminée, ce qu'il ne devrait pas faire exactement .
Martin Argerami
6

Edit: J'ai totalement mal compris la question. Dans le navigateur, j'utiliserais setTimeout. S'il était important qu'il s'exécute dans un autre thread, j'utiliserais Web Workers .

Linus Thiel
la source
1
? Cela ne fait pas une fonction asynchrone: O
markzzz
6

En retard, mais pour montrer une solution facile à utiliser promisesaprès leur introduction dans ES6 , il gère beaucoup plus facilement les appels asynchrones:

Vous définissez le code asynchrone dans une nouvelle promesse:

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

Remarque à définir resolve()lorsque l'appel asynchrone se termine.
Ensuite, vous ajoutez le code que vous souhaitez exécuter après la fin de l'appel async à l'intérieur .then()de la promesse:

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

En voici un extrait:

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

ou JSFiddle

hd84335
la source
6

Cette page vous guide à travers les bases de la création d'une fonction javascript asynchrone.

Depuis ES2017, les fonctions javacript asynchrones sont beaucoup plus faciles à écrire. Vous devriez également en savoir plus sur les promesses .

Shanabus
la source
Le lien est mort.
Martin Argerami
3

Si vous souhaitez utiliser des paramètres et réguler le nombre maximum de fonctions asynchrones, vous pouvez utiliser un simple worker async que j'ai construit:

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

Vous pouvez l'utiliser comme ceci:

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

Bien que n'étant pas fondamentalement différente des autres solutions, je pense que ma solution fait quelques bonnes choses supplémentaires:

  • il permet de paramétrer les fonctions
  • il passe la sortie de la fonction au callback
  • il est ajouté pour Function.prototypepermettre une meilleure façon de l'appeler

De plus, la similitude avec la fonction intégrée Function.prototype.applyme semble appropriée.

Benjamin Kuykendall
la source
1

À côté de la bonne réponse de @pimvdb, et juste au cas où vous vous le demanderiez, async.js n'offre pas non plus de fonctions vraiment asynchrones. Voici une version (très) dépouillée de la méthode principale de la bibliothèque:

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

Ainsi, la fonction protège des erreurs et la transmet gracieusement au rappel à gérer, mais le code est aussi synchrone que toute autre fonction JS.

Mike M
la source
1

Malheureusement, JavaScript ne fournit pas de fonctionnalité asynchrone. Cela ne fonctionne que dans un seul thread. Mais la plupart des navigateurs modernes fournissent des Workers , qui sont des seconds scripts qui sont exécutés en arrière-plan et peuvent renvoyer un résultat. Donc, j'ai atteint une solution, je pense qu'il est utile d'exécuter une fonction de manière asynchrone, ce qui crée un worker pour chaque appel asynchrone.

Le code ci-dessous contient la fonction async à appeler en arrière-plan.

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

Voici un exemple d'utilisation:

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

Cela exécute une fonction qui itère un foravec un nombre énorme pour prendre du temps comme démonstration d'asynchronicité, puis obtient le double du nombre passé. La asyncméthode fournit un functionqui appelle la fonction souhaitée en arrière-plan, et dans ce qui est fourni comme paramètre de asynccallback, le returndans son paramètre unique. Donc, dans la fonction de rappel, j'ai alertle résultat.

Davide Cannizzo
la source
0

MDN a un bon exemple sur l'utilisation de setTimeout en préservant "this".

Comme le suivant:

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

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