web-dev-qa-db-fra.com

Comment créer une fonction asynchrone en Javascript?

Découvrez ceci 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 constater dans la console, la fonction "animate" est asynchrone et "branche" 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 ma function asyncFunct() { } avec ce comportement, comment puis-je le faire avec javascript/jquery? Je pense qu'il existe une stratégie sans l'utilisation de setTimeout()

134
markzzz

Vous ne pouvez pas créer une fonction asynchrone vraiment personnalisée. Vous devrez éventuellement utiliser une technologie fournie nativement, 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
  • Technologies supportant onload
  • ... beaucoup d'autres

En fait, pour l'animation jQuery tilisesetInterval.

175
pimvdb

Vous pouvez utiliser une minuterie:

setTimeout( yourFn, 0 );

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

ou, avec Lodash :

_.defer( yourFn );

Diffère l'appel de la func jusqu'à ce que la pile d'appels en cours soit effacée. Tous les arguments supplémentaires sont fournis à func lors de son invocation.

64
Šime Vidas

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

Et voici la solution ci-dessus:

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

TEST 1 ( peut indiquer '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 simule une tâche asynchrone

28
fider

Voici une fonction qui prend une autre fonction et génère une version 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();
});

Ceci diffère de la réponse de @ fider car la fonction elle-même a sa propre structure (aucun rappel n'a été ajouté, elle est déjà dans la fonction) et également parce qu'elle crée une nouvelle fonction pouvant être utilisée.

9
Ethan McTague

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 .

Cette page vous explique les bases de la création d’une fonction javascript async.

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

5
shanabus

En retard, mais pour montrer une solution facile en utilisant promises aprè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 pour définir resolve() à la fin de l'appel asynchrone.
Ensuite, vous ajoutez le code que vous souhaitez exécuter après la fin de l'appel asynchrone dans .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

4
hd84335

Si vous souhaitez utiliser les paramètres et régler le nombre maximal de fonctions asynchrones, vous pouvez utiliser un outil de traitement async simple 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);
3
João Alves
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 apporte quelques améliorations supplémentaires:

  • il permet de paramétrer les fonctions
  • il passe la sortie de la fonction au rappel
  • il est ajouté à Function.prototype permettant ainsi de l'appeler plus facilement

De plus, la similarité avec la fonction intégrée Function.prototype.apply me semble appropriée.

2

En plus de l'excellente réponse de @pimvdb, et juste au cas où vous vous le demanderiez, async.js n'offre pas non plus de fonctions véritablement asynchrones. Voici une version (très) simplifié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);
    };
}

Donc, la fonction protège des erreurs et la remet gracieusement au callback, mais le code est aussi synchrone que toute autre fonction JS.

1
Mike M

Malheureusement, JavaScript ne fournit pas de fonctionnalité asynchrone. Cela ne fonctionne que dans un seul thread. Mais la plupart des navigateurs modernes fournissent Worker s , qui sont des seconds scripts exécutés en arrière-plan et pouvant renvoyer un résultat. J'ai donc trouvé une solution qui, selon moi, est utile pour exécuter une fonction de manière asynchrone, ce qui crée un opérateur pour chaque appel asynchrone.

Le code ci-dessous contient la fonctionasyncappeler 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);
};

Ceci est un exemple d'utilisation:

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

Ceci exécute une fonction qui itère une for avec un nombre énorme pour prendre du temps en tant que démonstration d'asynchronicité, puis obtient le double du nombre passé. La méthode async fournit une function qui appelle la fonction voulue en arrière-plan et qui fournit le paramètre de async rappelle la return dans son paramètre unique. Donc, dans la fonction de rappel I alert le résultat.

1
Davide Cannizzo

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

Comme ce qui suit:

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

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