web-dev-qa-db-fra.com

Comment renvoyer la réponse d'un appel asynchrone?

J'ai une fonction foo qui fait une demande Ajax. Comment puis-je retourner la réponse de foo?

J'ai essayé de renvoyer la valeur à partir du rappel success, ainsi que d'assigner la réponse à une variable locale de la fonction et de la renvoyer, mais aucun de ces moyens ne renvoie la réponse.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.
5085
Felix Kling

→ Pour une explication plus générale du comportement asynchrone avec différents exemples, veuillez consulter Pourquoi ma variable est-elle inchangée après que je la modifie à l'intérieur d'une fonction? - Référence de code asynchrone

→ Si vous comprenez déjà le problème, passez aux solutions possibles ci-dessous.

Le problème

Le A en Ajax signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple, _$.ajax_ retourne immédiatement et l'instruction suivante, _return result;_, est exécutée avant que la fonction que vous avez transmise en tant que rappel success ne soit même appelée.

Voici une analogie qui, espérons-le, rend la différence entre flux synchrone et asynchrone plus claire:

Synchrone

Imaginez que vous appeliez un ami et lui demandiez de rechercher quelque chose pour vous. Bien que cela puisse prendre un certain temps, vous attendez au téléphone et fixez l'espace, jusqu'à ce que votre ami vous donne la réponse dont vous aviez besoin.

La même chose se produit lorsque vous effectuez un appel de fonction contenant le code "normal":

_function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();
_

Bien que l'exécution de findItem puisse prendre beaucoup de temps, tout code venant après var item = findItem(); doit être wait jusqu'à ce que la fonction retourne le résultat.

Asynchrone

Vous appelez à nouveau votre ami pour la même raison. Mais cette fois, vous lui dites que vous êtes pressé et qu'il devrait vous rappeler sur votre téléphone mobile. Vous raccrochez, quittez la maison et faites ce que vous aviez prévu de faire. Une fois que votre ami vous rappelle, vous traitez avec les informations qu'il vous a données.

C'est exactement ce qui se passe lorsque vous faites une demande Ajax.

_findItem(function(item) {
    // Do something with item
});
doSomethingElse();
_

Au lieu d'attendre la réponse, l'exécution continue immédiatement et l'instruction après l'exécution de l'appel Ajax. Pour obtenir la réponse éventuellement, vous indiquez une fonction à appeler une fois la réponse reçue, un callback (remarquez quelque chose? call back ?). Toute instruction venant après cet appel est exécutée avant l'appel du rappel.


Solutions)

Adoptez la nature asynchrone de JavaScript! Bien que certaines opérations asynchrones fournissent des contreparties synchrones (tout comme "Ajax"), il est généralement déconseillé de les utiliser, notamment dans les cas suivants: contexte du navigateur.

Pourquoi est-ce mauvais, demandez-vous?

JavaScript s'exécute dans le thread d'interface utilisateur du navigateur et tout processus long verrouille l'interface utilisateur, ce qui la rend inactive. En outre, le temps d'exécution de JavaScript est limité et le navigateur demande à l'utilisateur s'il souhaite ou non poursuivre l'exécution.

Tout cela constitue une très mauvaise expérience utilisateur. L'utilisateur ne pourra pas dire si tout fonctionne bien ou non. De plus, l’effet sera pire pour les utilisateurs avec une connexion lente.

Dans ce qui suit, nous examinerons trois solutions différentes qui se construisent les unes sur les autres:

  • Promesses avec _async/await_ (ES2017 +, disponible dans les navigateurs plus anciens si vous utilisez un transpiler ou un régénérateur)
  • Callbacks (populaire dans les nœuds)
  • Promises avec then() (ES2015 +, disponible dans les navigateurs plus anciens si vous utilisez l'une des nombreuses bibliothèques de promesses)

Tous les trois sont disponibles dans les navigateurs actuels, ainsi que sur le noeud 7 +.


ES2017 +: promesses avec async/await

La version ECMAScript publiée en 2017 a introduit support au niveau de la syntaxe pour les fonctions asynchrones. À l'aide de async et await, vous pouvez écrire de manière asynchrone dans un "style synchrone". Le code est toujours asynchrone, mais il est plus facile à lire/à comprendre.

_async/await_ s'appuie sur les promesses: une fonction async renvoie toujours une promesse. await "déballe" une promesse et aboutit à la valeur avec laquelle la promesse a été résolue ou génère une erreur si la promesse est rejetée.

Important: Vous ne pouvez utiliser que await dans une fonction async. À l'heure actuelle, await de niveau supérieur n'est pas encore pris en charge. Vous devrez donc peut-être créer un IIFE asynchrone ( Expression de fonction immédiate ) pour démarrer un contexte async.

Vous pouvez en savoir plus sur async et await sur MDN.

Voici un exemple qui se base sur le délai ci-dessus:

_// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();
_

Les versions actuelles du navigateur et du noeud prennent en charge _async/await_. Vous pouvez également prendre en charge des environnements plus anciens en transformant votre code en ES5 à l'aide du régénérateur (ou d'outils utilisant ce régénérateur, tel que Babel ).


Laisser les fonctions accepter callbacks

Un rappel est simplement une fonction transmise à une autre fonction. Cette autre fonction peut appeler la fonction transmise à chaque fois qu'elle est prête. Dans le contexte d'un processus asynchrone, le rappel sera appelé chaque fois que le processus asynchrone est terminé. Habituellement, le résultat est transmis au rappel.

Dans l'exemple de la question, vous pouvez faire en sorte que foo accepte un rappel et l'utilise comme rappel success. Donc ça

_var result = foo();
// Code that depends on 'result'
_

devient

_foo(function(result) {
    // Code that depends on 'result'
});
_

Ici nous avons défini la fonction "inline" mais vous pouvez passer n'importe quelle référence de fonction:

_function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);
_

foo lui-même est défini comme suit:

_function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}
_

callback fera référence à la fonction que nous passons à foo lorsque nous l'appelons et nous la transmettons simplement à success. C'est à dire. une fois que la demande Ajax a abouti, _$.ajax_ appelle callback et transmet la réponse au rappel (auquel on peut faire référence avec result, puisque c'est ainsi que nous avons défini le rappel).

Vous pouvez également traiter la réponse avant de la transmettre au rappel:

_function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}
_

Il est plus facile d'écrire du code à l'aide de rappels que cela puisse paraître. Après tout, JavaScript dans le navigateur est fortement basé sur les événements (événements DOM). Recevoir la réponse Ajax n'est rien d'autre qu'un événement.
Des problèmes peuvent survenir lorsque vous devez travailler avec du code tiers, mais la plupart des problèmes peuvent être résolus simplement en réfléchissant au processus d'application.


ES2015 +: promesses avec then ()

L'API Promise est une nouvelle fonctionnalité d'ECMAScript 6 (ES2015), mais elle dispose déjà d'un bon support du navigateur . De nombreuses bibliothèques implémentent également l’API standard Promises et fournissent des méthodes supplémentaires pour faciliter l’utilisation et la composition de fonctions asynchrones (par exemple bluebird ).

Les promesses sont des conteneurs pour les valeurs future . Lorsque la promesse reçoit la valeur (c'est résolu ) ou lorsqu'elle est annulée ( rejeté ), il en avertit tous les "" "auditeurs" qui veulent accéder à cette valeur.

L'avantage par rapport aux rappels en clair est qu'ils vous permettent de découpler votre code et qu'ils sont plus faciles à composer.

Voici un exemple simple d'utilisation d'une promesse:

_function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });
_

Appliqué à notre appel Ajax, nous pourrions utiliser des promesses comme celle-ci:

_function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });
_

Décrire tous les avantages offerts par promesse dépasse le cadre de cette réponse, mais si vous écrivez un nouveau code, vous devez l’envisager sérieusement. Ils fournissent une grande abstraction et séparation de votre code.

Plus d'informations sur les promesses: HTML5 roches - Promesses JavaScript

Note latérale: Les objets différés de jQuery

Les objets différés sont des implémentations personnalisées de promesses par jQuery (avant la normalisation de l'API Promise). Ils se comportent presque comme des promesses mais exposent une API légèrement différente.

Chaque méthode Ajax de jQuery renvoie déjà un "objet différé" (en fait une promesse d'objet différé) que vous pouvez simplement renvoyer à partir de votre fonction:

_function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});
_

Note latérale: les promesses

Gardez à l'esprit que les promesses et les objets différés ne sont que conteneurs pour une valeur future, ils ne sont pas la valeur elle-même. Par exemple, supposons que vous ayez:

_function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}
_

Ce code comprend mal les problèmes d'asynchronie ci-dessus. Plus précisément, $.ajax() ne gèle pas le code pendant la vérification de la page '/ mot de passe' sur votre serveur. Il envoie une requête au serveur et pendant l'attente, renvoie immédiatement un objet jQuery Ajax Deferred, et non la réponse de le serveur. Cela signifie que l'instruction if obtiendra toujours cet objet différé, la traitera comme true et procédera comme si l'utilisateur était connecté. Mauvais.

Mais la solution est facile:

_checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});
_

Non recommandé: appels "Ajax" synchrones

Comme je l'ai mentionné, certaines (!) Opérations asynchrones ont des contreparties synchrones. Je ne préconise pas leur utilisation, mais par souci d'exhaustivité, voici comment procéder pour effectuer un appel synchrone:

Sans jQuery

Si vous utilisez directement un objet XMLHTTPRequest , transmettez false en tant que troisième argument à .open .

jQuery

Si vous utilisez jQuery , vous pouvez définir l’option async sur false. Notez que cette option est obsolète depuis jQuery 1.8. Vous pouvez alors toujours utiliser un rappel success ou accéder à la propriété responseText de l'objet jqXHR :

_function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}
_

Si vous utilisez une autre méthode Ajax jQuery, telle que _$.get_, _$.getJSON_, etc., vous devez la changer en _$.ajax_ (puisque vous ne pouvez transmettre que les paramètres de configuration à _$.ajax_).

Attention! Il n'est pas possible de faire une demande synchrone JSONP . JSONP, de par sa nature même, est toujours asynchrone (une raison de plus pour ne même pas envisager cette option).

5361
Felix Kling

Si vous n'utilisez pas jQuery dans votre code, cette réponse est pour vous.

Votre code devrait être quelque chose dans le sens de ceci:

_function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'
_

Felix Kling a très bien écrit les réponses aux personnes utilisant jQuery pour AJAX. J'ai décidé de proposer une solution de remplacement aux personnes qui ne le sont pas.

( Remarque, pour ceux qui utilisent la nouvelle fetch API, Angular ou les promesses que j'ai ajoutées une autre réponse ci-dessous )


À quoi tu fais face

Ceci est un court résumé de "Explication du problème" de l'autre réponse. Si vous n'êtes pas sûr après avoir lu ceci, lisez-le.

Le A dans AJAX signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple, .send est renvoyé immédiatement et l'instruction suivante, _return result;_, est exécutée avant que la fonction transmise en tant que success callback n'ait été appelée.

Cela signifie que lorsque vous revenez, l'écouteur que vous avez défini ne s'est pas encore exécuté, ce qui signifie que la valeur que vous renvoyez n'a pas encore été définie.

Voici une analogie simple

_function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}
_

(Violon)

La valeur de a renvoyée est undefined puisque la partie _a=5_ n'a pas encore été exécutée. AJAX agit comme ceci: vous renvoyez la valeur avant que le serveur ait la chance de dire à votre navigateur quelle est cette valeur.

Une solution possible à ce problème consiste à coder de manière réactive , en indiquant à votre programme quoi faire lorsque le calcul est terminé.

_function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}
_

Ceci s'appelle CPS . Fondamentalement, nous passons getFive une action à exécuter à la fin. Nous expliquons à notre code comment réagir à la fin d’un événement (comme notre appel AJAX, ou dans ce cas, le délai ).

L'utilisation serait:

_getFive(onComplete);
_

Ce qui devrait alerter "5" sur l'écran. (Violon) .

Solutions possibles

Il existe fondamentalement deux façons de résoudre ce problème:

  1. Rendre le AJAX appel synchrone (appelons-le SJAX).
  2. Restructurez votre code pour qu'il fonctionne correctement avec les rappels.

1. Synchrone AJAX - Ne le faites pas !!

Quant à AJAX synchrone, ne le faites pas! La réponse de Felix soulève des arguments convaincants sur la raison pour laquelle c'est une mauvaise idée. Pour résumer, cela gèlera le navigateur de l'utilisateur jusqu'à ce que le serveur renvoie la réponse et créera une très mauvaise expérience utilisateur. Voici un autre court résumé tiré de MDN expliquant pourquoi:

XMLHttpRequest prend en charge les communications synchrones et asynchrones. En règle générale, toutefois, les demandes asynchrones doivent être préférées aux demandes synchrones pour des raisons de performances.

En bref, les requêtes synchrones bloquent l'exécution du code ... ... cela peut causer de sérieux problèmes ...

Si vous devez le faire, vous pouvez passer un drapeau: voici comment:

_var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}
_

2. Code de restructuration

Laissez votre fonction accepter un rappel. Dans l'exemple, le code foo peut être configuré pour accepter un rappel. Nous dirons à notre code comment réagir à la fin de foo.

Alors:

_var result = foo();
// code that depends on `result` goes here
_

Devient:

_foo(function(result) {
    // code that depends on `result`
});
_

Ici, nous avons passé une fonction anonyme, mais nous pourrions tout aussi facilement passer une référence à une fonction existante, en lui donnant l'aspect suivant:

_function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);
_

Pour plus de détails sur la manière dont ce type de conception de rappel est effectué, consultez la réponse de Felix.

Maintenant, définissons foo lui-même pour agir en conséquence

_function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}
_

(violon)

Nous avons maintenant forcé notre fonction foo à accepter une action à exécuter lorsque AJAX se termine avec succès. Nous pouvons l'étendre davantage en vérifiant si le statut de la réponse n'est pas 200 et en agissant en conséquence (création d'un gestionnaire d'échec, etc.). Résoudre efficacement notre problème.

Si vous avez encore du mal à comprendre cela lisez le AJAX Guide de démarrage rapide sur MDN.

1029

XMLHttpRequest 2 (lisez tout d'abord les réponses de Benjamin Gruenbaum & Felix Kling )

Si vous n'utilisez pas jQuery et que vous voulez un Nice short XMLHttpRequest 2 qui fonctionne sur les navigateurs modernes ainsi que sur les navigateurs mobiles, je suggère de l'utiliser de cette façon:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

Comme vous pouvez le voir:

  1. C'est plus court que toutes les autres fonctions listées.
  2. Le rappel est défini directement (donc pas de fermetures inutiles).
  3. Il utilise le nouveau onload (vous n'avez donc pas à vérifier l'état de readystate &&)
  4. Il y a quelques autres situations dont je ne me souviens pas qui rendent XMLHttpRequest 1 ennuyeux.

Il existe deux manières d'obtenir la réponse de cet appel Ajax (trois à l'aide du nom XMLHttpRequest var):

Le plus simple:

this.response

Ou si pour une raison quelconque vous bind() le rappel à une classe:

e.target.response

Exemple:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

Ou (celle ci-dessus est meilleure, les fonctions anonymes sont toujours un problème):

ajax('URL', function(e){console.log(this.response)});

Rien de plus facile.

Maintenant, certaines personnes diront probablement qu'il vaut mieux utiliser onreadystatechange ou même le nom de variable XMLHttpRequest. C'est faux.

Départ fonctionnalités avancées de XMLHttpRequest

Il supportait tous les * navigateurs modernes. Et je peux confirmer que j'utilise cette approche puisque XMLHttpRequest 2 existe. Je n'ai jamais eu aucun type de problème sur tous les navigateurs que j'utilise.

onreadystatechange n'est utile que si vous souhaitez obtenir les en-têtes sur l'état 2.

L'utilisation du nom de variable XMLHttpRequest est une autre grosse erreur, car vous devez exécuter le rappel à l'intérieur des fermetures onload/oreadystatechange, sinon vous l'avez perdu.


Maintenant, si vous voulez quelque chose de plus complexe en utilisant post et FormData, vous pouvez facilement étendre cette fonction:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

Encore une fois ... c'est une fonction très courte, mais elle reçoit et publie.

Exemples d'utilisation:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

Ou passez un élément de formulaire complet (document.getElementsByTagName('form')[0]):

var fd = new FormData(form);
x(url, callback, 'post', fd);

Ou définissez des valeurs personnalisées:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

Comme vous pouvez le constater, je n'ai pas implémenté la synchronisation ... c'est une mauvaise chose.

Cela dit, pourquoi ne pas le faire facilement?


Comme mentionné dans le commentaire, l'utilisation de error && synchrone fait complètement éclater le point de la réponse. Quel est le bon moyen d'utiliser correctement Ajax?

Gestionnaire d'erreur

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

Dans le script ci-dessus, vous avez un gestionnaire d’erreur qui est défini de manière statique afin de ne pas compromettre la fonction. Le gestionnaire d'erreurs peut également être utilisé pour d'autres fonctions.

Cependant, pour réellement obtenir une erreur, le moyen uniquement consiste à écrire une URL incorrecte, auquel cas chaque navigateur génère une erreur.

Les gestionnaires d'erreurs sont peut-être utiles si vous définissez des en-têtes personnalisés, définissez le responseType sur le tampon de tableau blob ou autre chose ...

Même si vous passez "POSTAPAPAP" comme méthode, cela ne produira pas d'erreur.

Même si vous transmettez 'fdggdgilfdghfldj' en tant que formdata, cela ne produira pas d'erreur.

Dans le premier cas, l'erreur est à l'intérieur de la displayAjax() sous this.statusText en tant que Method not Allowed.

Dans le second cas, cela fonctionne simplement. Vous devez vérifier côté serveur si vous avez transmis les bonnes données de publication.

inter-domaine non autorisé génère une erreur automatiquement.

Dans la réponse d'erreur, il n'y a pas de codes d'erreur.

Il n'y a que le this.type qui est réglé sur erreur.

Pourquoi ajouter un gestionnaire d’erreurs si vous n’avez aucun contrôle sur les erreurs? La plupart des erreurs sont renvoyées à l'intérieur dans la fonction de rappel displayAjax().

Donc: pas besoin de vérification d'erreur si vous êtes capable de copier et coller l'URL correctement. ;)

PS: En tant que premier test, j’ai écrit x ('x', displayAjax) ..., et il a totalement obtenu une réponse ... ??? J'ai donc vérifié le dossier dans lequel se trouve le code HTML et il y avait un fichier appelé 'x.xml'. Ainsi, même si vous oubliez l'extension de votre fichier, XMLHttpRequest 2 LE TROUVERA . J'ai raté


Lire un fichier synchrone

Ne faites pas ça.

Si vous souhaitez bloquer le navigateur pendant un moment, chargez un fichier synchrone Nice big .txt.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

Maintenant tu peux faire

 var res = omg('thisIsGonnaBlockThePage.txt');

Il n'y a pas d'autre moyen de faire cela de manière non asynchrone. (Oui, avec la boucle setTimeout ... mais sérieusement?)

Un autre point est ... si vous travaillez avec des API ou simplement avec les fichiers de votre propre liste ou quoi que ce soit, vous utilisez toujours des fonctions différentes pour chaque requête ...

Seulement si vous avez une page où vous chargez toujours le même XML/JSON ou tout ce dont vous n'avez besoin que d'une seule fonction. Dans ce cas, modifiez un peu la fonction Ajax et remplacez b par votre fonction spéciale.


Les fonctions ci-dessus sont pour une utilisation de base.

Si vous voulez étendre la fonction ...

Oui, vous pouvez.

J'utilise beaucoup d'API et l'une des premières fonctions que j'intègre dans chaque page HTML est la première fonction Ajax dans cette réponse, avec uniquement GET ...

Mais vous pouvez faire beaucoup de choses avec XMLHttpRequest 2:

J'ai créé un gestionnaire de téléchargement (utilisant des plages des deux côtés avec resume, filereader, système de fichiers), divers convertisseurs de redimensionneurs d'images utilisant canvas, peuplant des bases de données SQL Web avec base64images et bien plus encore ... Mais dans ces cas, vous devez créer une fonction uniquement pour cela. but ... parfois vous avez besoin d'un blob, de tampons de tableaux, vous pouvez définir des en-têtes, remplacer le type MIME et bien plus encore ...

Mais la question ici est de savoir comment renvoyer une réponse Ajax ... (j'ai ajouté un moyen simple).

378
cocco

Si vous utilisez des promesses, cette réponse est pour vous.

Cela signifie AngularJS, jQuery (avec différé), le remplacement natif de XHR (fetch), EmberJS, la sauvegarde de BackboneJS ou toute bibliothèque de noeuds renvoyant des promesses.

Votre code devrait être quelque chose dans le sens de ceci:

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

Felix Kling a très bien écrit les réponses aux personnes utilisant jQuery avec des rappels pour AJAX. J'ai une réponse pour XHR native. Cette réponse concerne l’utilisation générique des promesses, que ce soit en amont ou en aval.


La question centrale

Le modèle de concurrence de JavaScript dans le navigateur et sur le serveur avec NodeJS/io.js est asynchrone et réactif .

Chaque fois que vous appelez une méthode qui retourne une promesse, les gestionnaires then sont toujours exécutés de manière asynchrone - c'est-à-dire après le code situé en dessous d'eux qui ne se trouve pas dans un gestionnaire .then.

Cela signifie que lorsque vous renvoyez data, le gestionnaire then que vous avez défini ne s’est pas encore exécuté. Cela signifie à son tour que la valeur que vous renvoyez n'a pas été définie correctement dans le temps.

Voici une analogie simple pour le problème:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

La valeur de data est undefined puisque la partie data = 5 n'a pas encore été exécutée. Il sera probablement exécuté dans une seconde, mais à ce moment-là, il n’est plus pertinent pour la valeur renvoyée.

Étant donné que l'opération ne s'est pas encore produite (AJAX, appel sur le serveur, IO, timer), vous renvoyez la valeur avant que la demande ait la possibilité d'indiquer à votre code quelle est cette valeur.

Une solution possible à ce problème consiste à coder de manière réactive , en indiquant à votre programme quoi faire lorsque le calcul est terminé. Les promesses le permettent activement en étant de nature temporelle (sensible au temps).

Récapitulation rapide des promesses

Une promesse est une valeur dans le temps . Les promesses ont un état, elles démarrent en attente sans valeur et peuvent se régler comme suit:

  • rempli signifie que le calcul s'est terminé avec succès.
  • rejeté , ce qui signifie que le calcul a échoué.

Une promesse ne peut changer d'états qu'une seule fois , après quoi elle restera toujours dans le même état pour toujours. Vous pouvez attacher les gestionnaires then aux promesses d’extraire leur valeur et de gérer les erreurs. then les gestionnaires permettent chaîner d'appels. Les promesses sont créées par à l'aide d'API qui les renvoient . Par exemple, le plus moderne AJAX _ remplacement fetch ou le $.get de jQuery retournent les promesses.

Lorsque nous appelons .then sur une promesse et que renvoie quelque chose , nous obtenons une promesse pour la valeur traitée . Si nous retournons une autre promesse, nous obtiendrons des choses incroyables, mais tenons nos chevaux.

Avec des promesses

Voyons comment nous pouvons résoudre le problème ci-dessus avec des promesses. Tout d'abord, démontrons notre compréhension des états de promesse d'en haut en utilisant le constructeur Promise pour créer une fonction de retard:

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

Maintenant, après avoir converti setTimeout pour utiliser les promesses, nous pouvons utiliser then pour le faire compter:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

Fondamentalement, au lieu de renvoyer une valeur , ce que nous ne pouvons pas faire à cause du modèle de simultanéité - nous renvoyons un wrapper pour une valeur que nous pouvons décompresser avec then. C'est comme une boîte que vous pouvez ouvrir avec then.

En appliquant cette

Il en va de même pour votre appel d'API d'origine, vous pouvez:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

Donc, cela fonctionne aussi bien. Nous avons appris que nous ne pouvons pas renvoyer de valeurs d'appels déjà asynchrones, mais que nous pouvons utiliser des promesses et les chaîner pour effectuer le traitement. Nous savons maintenant comment renvoyer la réponse d'un appel asynchrone.

ES2015 (ES6)

ES6 introduit générateurs qui sont des fonctions qui peuvent revenir au milieu, puis reprendre le point où elles se trouvaient. Ceci est généralement utile pour les séquences, par exemple:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

Est une fonction qui renvoie un itérateur sur la séquence 1,2,3,3,3,3,.... qui peut être itérée. Bien que cela soit intéressant en lui-même et ouvre la voie à de nombreuses possibilités, il existe un cas intéressant en particulier.

Si la séquence que nous produisons est une séquence d'actions plutôt que de chiffres, nous pouvons suspendre la fonction à chaque fois qu'une action est générée et l'attendre avant de reprendre la fonction. Ainsi, au lieu d’une suite de nombres, nous avons besoin d’une suite de valeurs futures - c’est-à-dire: promesses.

Cette astuce un peu délicate mais très puissante nous permet d’écrire du code asynchrone de manière synchrone. Il y a plusieurs "coureurs" qui le font pour vous. Écrire une ligne ne représente que quelques lignes de code, mais dépasse le cadre de cette réponse. J'utiliserai le Promise.coroutine de Bluebird ici, mais il existe d'autres wrappers comme co ou Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

Cette méthode retourne une promesse elle-même, que nous pouvons consommer d'autres coroutines. Par exemple:

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

Dans ES7, ceci est davantage standardisé, il y a plusieurs propositions pour le moment, mais vous pouvez toutes les promettre await. Ceci est juste "sucre" (syntaxe plus agréable) pour la proposition ES6 ci-dessus en ajoutant les mots-clés async et await. Voici l'exemple ci-dessus:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

Il retourne toujours une promesse quand même :)

303

Vous utilisez mal Ajax. L'idée n'est pas de lui renvoyer quoi que ce soit, mais de transférer les données à une fonction appelée fonction de rappel, qui gère les données.

C'est:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

Renvoyer quoi que ce soit dans le gestionnaire de soumission ne fera rien. Au lieu de cela, vous devez soit transférer les données, soit en faire ce que vous voulez directement dans la fonction de réussite.

238
Nic

La solution la plus simple consiste à créer une fonction JavaScript et à l'appeler pour le rappel Ajax success.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 
225
Hemant Bavle

Je vais répondre avec une bande dessinée horrible, dessinée à la main. La seconde image est la raison pour laquelle result est undefined dans votre exemple de code.

enter image description here

206
Johannes Fahrenkrug

Angulaire1

Pour les personnes qui utilisent AngularJS , pouvez gérer cette situation en utilisant Promises.

Ici ça dit,

Les promesses peuvent être utilisées pour désanalyser des fonctions asynchrones et permettent de chaîner plusieurs fonctions ensemble.

Vous pouvez trouver une bonne explication ici aussi.

Exemple trouvé dans docs mentionné ci-dessous.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 et plus tard

Dans Angular2 avec l'exemple suivant, mais recommandé utiliser Observables avec Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

Vous pouvez consommer cela de cette façon,

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

Voir le original post ici. Mais TypeScript ne supporte pas Promesses es6 natives , si vous voulez l’utiliser, vous aurez peut-être besoin d’un plugin pour cela.

De plus, voici les promesses spec définir ici.

151
Maleen Abewardana

La plupart des réponses ici donnent des suggestions utiles lorsque vous avez une seule opération asynchrone, mais parfois, cela se produit lorsque vous devez effectuer une opération asynchrone pour chaque entrée dans un tableau ou une autre structure semblable à une liste. La tentation est de faire ceci:

_// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.
_

Exemple:

_// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

La raison pour laquelle cela ne fonctionne pas, c'est que les rappels de doSomethingAsync n'ont pas encore été exécutés au moment où vous essayez d'utiliser les résultats.

Ainsi, si vous avez un tableau (ou une liste quelconque) et que vous souhaitez effectuer des opérations asynchrones pour chaque entrée, vous avez deux options: Effectuez les opérations en parallèle (chevauchement) ou en série (une après l'autre en séquence).

Parallèle

Vous pouvez tous les lancer et noter le nombre de rappels que vous attendez, puis utiliser les résultats lorsque vous avez reçu autant de rappels:

_var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});
_

Exemple:

_var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

(On pourrait supprimer expecting et utiliser simplement _results.length === theArray.length_, mais cela laisse entrevoir la possibilité que theArray soit modifié alors que les appels sont en attente ...)

Remarquez comment nous utilisons les variables index de forEach pour enregistrer le résultat dans results dans la même position que celle à laquelle elle se rapporte, même si les résultats arrivent en panne (les appels asynchrones ne se terminant pas nécessairement dans l'ordre dans lequel ils ont été démarrés) ).

Mais que se passe-t-il si vous devez renvoyer les résultats d'une fonction? Comme les autres réponses l'ont souligné, vous ne pouvez pas; votre fonction doit accepter et appeler un rappel (ou renvoyer un Promise ). Voici une version de rappel:

_function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});
_

Exemple:

_function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

Ou voici une version renvoyant un Promise à la place:

_function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});
_

Bien sûr, si doSomethingAsync nous a transmis des erreurs, nous utiliserions reject pour rejeter la promesse lorsque nous aurions une erreur.)

Exemple:

_function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

(Ou alternativement, vous pouvez créer un wrapper pour doSomethingAsync qui retourne une promesse, puis faire ce qui suit ci-dessous ...)

Si doSomethingAsync vous donne un Promise , vous pouvez utiliser Promise.all :

_function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});
_

Si vous savez que doSomethingAsync ignorera les deuxième et troisième arguments, vous pouvez simplement le transmettre directement à map (map appelle son rappel avec trois arguments, mais la plupart des gens n'utilisent que le premier la plupart du temps):

_function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});
_

Exemple:

_function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

Notez que _Promise.all_ résout sa promesse avec un tableau des résultats de toutes les promesses que vous lui donnez quand elles sont toutes résolues, ou rejette sa promesse lorsque le premier des promesses que vous lui faites est rejetée.

Séries

Supposons que vous ne vouliez pas que les opérations soient en parallèle? Si vous souhaitez les exécuter les unes après les autres, vous devez attendre la fin de chaque opération avant de commencer la suivante. Voici un exemple de fonction qui fait cela et appelle un rappel avec le résultat:

_function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.Push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});
_

(Puisque nous effectuons le travail en série, nous pouvons simplement utiliser results.Push(result) puisque nous savons que les résultats ne seront pas erronés. Dans ce qui précède, nous aurions pu utiliser _results[index] = result;_, mais dans certains des exemples suivants, nous n'avons pas d'index à utiliser.)

Exemple:

_function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.Push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

(Ou encore, construisez un wrapper pour doSomethingAsync qui vous donne une promesse et faites ce qui est en dessous ...)

Si doSomethingAsync vous donne une promesse, si vous pouvez utiliser la syntaxe ES2017 + (peut-être avec un transpiler tel que Babel ), vous pouvez utiliser une fonction async avec for-of et await :

_async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.Push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});
_

Exemple:

_async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.Push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

Si vous ne pouvez pas (encore) utiliser la syntaxe ES2017 +, vous pouvez utiliser une variante du motif modèle "" Réduisez les promesses " (c'est plus complexe que la réduction habituelle de Promise, car nous ne transmettons pas le résultat de un dans l’autre, mais au lieu de rassembler leurs résultats dans un tableau):

_function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.Push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});
_

Exemple:

_function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.Push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_

... qui est moins encombrant avec ES2015 + fonctions de flèche :

_function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.Push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});
_

Exemple:

_function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.Push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}_
_.as-console-wrapper {
  max-height: 100% !important;
}_
138
T.J. Crowder

Regardez cet exemple:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

Comme vous pouvez le constater, getJoke est une promesse résolue (il est résolu lors du renvoi de res.data.value). Vous attendez donc que la demande $ http.get soit terminée, puis console.log (res.joke) est exécuté (en tant que flux asynchrone normal).

Ceci est la commande:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

chemin ES6 (asynchrone - attendre)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
104

Une autre approche pour renvoyer une valeur d'une fonction asynchrone consiste à transmettre un objet qui stockera le résultat de la fonction asynchrone.

Voici un exemple du même:

var async = require("async");

// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.Push(function(_callback){
    // some asynchronous operation
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;
            _callback();
        }
    });
});

async.parallel(asyncTasks, function(){
    // result is available after performing asynchronous operation
    console.log(result)
    console.log('Done');
});

J'utilise l'objet result pour stocker la valeur pendant l'opération asynchrone. Cela permet au résultat d'être disponible même après le travail asynchrone.

J'utilise beaucoup cette approche. Je serais intéressé de savoir comment cette approche fonctionne bien lorsque le résultat est renvoyé par le biais de modules consécutifs.

92
jsbisht

C’est l’un des endroits qui la liaison de données de deux manières qui est utilisé dans de nombreux nouveaux frameworks JavaScript fonctionnera parfaitement pour vous ...

Donc, si vous utilisez Angular, React ou tout autre framework qui le fait liaison de données à deux sens ou concept de magasin, ce problème est simplement résolu pour vous, Donc, dans Word facile, votre résultat est undefined à la première étape, vous avez donc result = undefined avant de recevoir les données, puis dès que vous obtenez le résultat, il sera mis à jour et attribué à la nouvelle valeur que la réponse de votre appel Ajax ...

Mais comment faire en pure javascript ou jQuery par exemple comme vous l'avez demandé dans cette question?

Vous pouvez utiliser un rappel, promesse et récemment observable pour le gérer pour vous, par exemple dans les promesses, nous avons une fonction telle que success() ou then() qui sera exécuté lorsque vos données seront prêtes, même avec rappel ou s'abonner fonction sur observable.

Par exemple, dans votre cas où vous utilisez jQuery, vous pouvez faire quelque chose comme ceci:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); //after we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); //fooDone has the data and console.log it
    };

    foo(); //call happens here
});

Pour plus d’informations, étudiez promesses et observables, des méthodes plus récentes pour effectuer cette opération asynchrone.

91
Alireza

Alors que les promesses et les rappels fonctionnent très bien dans de nombreuses situations, il est difficile d’exprimer quelque chose du genre:

if (!name) {
  name = async1();
}
async2(name);

Vous finiriez par passer par async1; vérifie si name n'est pas défini ou non et appelle le rappel en conséquence.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

Bien que cela soit correct dans les petits exemples, cela devient agaçant lorsque de nombreux cas similaires et le traitement des erreurs sont impliqués.

Fibers aide à résoudre le problème.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Vous pouvez commander le projet ici .

83
rohithpr

La réponse courte est, vous devez implémenter un rappel comme ceci:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});
78
Pablo Matias Gomez

L’exemple suivant que j’ai écrit montre comment

  • Gérer les appels HTTP asynchrones;
  • Attendre la réponse de chaque appel API;
  • Utilisez Promise pattern;
  • Utilisez le modèle Promise.all pour joindre plusieurs appels HTTP;

Cet exemple de travail est autonome. Il définira un objet requête simple utilisant l’objet window XMLHttpRequest pour effectuer des appels. Il définira une fonction simple consistant à attendre que de nombreuses promesses soient complétées.

Le contexte. L'exemple interroge le point final API Spotify Web afin de rechercher des objets playlist pour un ensemble donné de chaînes de requête:

[
 "search?type=playlist&q=%22Doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Pour chaque élément, une nouvelle promesse déclenche un bloc - ExecutionBlock, analyse le résultat, planifie un nouvel ensemble de promesses en fonction du tableau de résultats, c'est-à-dire une liste d'objets Spotify user et exécute le nouveau Appel HTTP dans la ExecutionProfileBlock de manière asynchrone.

Vous pouvez ensuite voir une structure Promise imbriquée, qui vous permet de générer plusieurs appels HTTP imbriqués complètement asynchrones et de joindre les résultats de chaque sous-ensemble d'appels via Promise.all.

NOTE Récente Spotify search Les API nécessiteront la spécification d'un jeton d'accès dans les en-têtes de requête:

-H "Authorization: Bearer {your access token}" 

Ainsi, pour exécuter l'exemple suivant, vous devez placer votre jeton d'accès dans les en-têtes de la requête:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.Push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

arr = [
    "search?type=playlist&q=%22Doom%20metal%22",
    "search?type=playlist&q=Adele"
]

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

J'ai longuement discuté de cette solution ici .

76
loretoparisi

Réponse de 2017: vous pouvez désormais faire exactement ce que vous voulez dans chaque navigateur et nœud actuels

C'est assez simple:

  • Retourner une promesse
  • Utilisez le 'wait' , qui indiquera à JavaScript d'attendre que la promesse soit résolue en une valeur (comme la réponse HTTP)
  • Ajoutez le mot-clé 'async' à la fonction parent

Voici une version de travail de votre code:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

wait est supporté par tous les navigateurs actuels et le noeud 8

72
mikemaccana

Vous pouvez utiliser cette bibliothèque personnalisée (écrite avec Promise) pour effectuer un appel distant.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Exemple d'utilisation simple:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
64
Vinoth Rajendran

Js est un single threaded.

Le navigateur peut être divisé en trois parties:

1) Boucle d'événement

2) API Web

3) file d'attente d'événements

La boucle d'événement est exécutée pour toujours, c'est-à-dire une sorte de boucle infinie. La file d'attente d'événement est l'endroit où toutes vos fonctions sont poussées sur un événement donné (exemple: clic). Cette file d'attente est exécutée une par une et mise en boucle d'événement pour exécuter cette fonction pour l'exécution suivante après exécution, cela signifie que l'exécution d'une fonction ne commence que lorsque celle-ci est exécutée dans la boucle d'événement.

Supposons maintenant que nous ayons placé deux fonctions dans une file d’attente, l’une pour obtenir des données du serveur et une autre utilisant ces données. Nous avons placé la fonction serverRequest () en file d’attente, puis la fonction utiliseData (). La fonction serverRequest se met en boucle d’événement et appelle le serveur car nous ne savons jamais combien de temps il faudra pour obtenir les données du serveur. Ce processus devrait donc prendre du temps. Nous occupons donc notre boucle d’événements en suspendant notre page, c’est là que le Web Les API entrent dans le rôle qu’elles prennent cette fonction de la boucle d’événement et traitent de la création d’une boucle d’événement libre du serveur afin que nous puissions exécuter la fonction suivante de la file d’attente. le gaspillage et l'exécution de la fonction suivante se poursuivent jusqu'à la fin de la file d'attente (cela s'appelle un appel asynchrone, c'est-à-dire que nous pouvons faire autre chose jusqu'à ce que nous obtenions des données).

Supposons que notre fonction serverRequest () contienne une instruction return dans un code. Lorsque nous récupérons des données à partir de l'API Web du serveur, il le place dans la file d'attente à la fin de la file d'attente. Comme il est poussé à la fin de la file d'attente, nous ne pouvons pas utiliser ses données car il n'y a plus de fonction dans notre file d'attente pour utiliser ces données. Il n'est donc pas possible de renvoyer quelque chose à partir de l'appel async.

Ainsi, la solution est un rappel ou promis .

Une image d'une des réponses ici, explique correctement l'utilisation du rappel ... Nous donnons notre fonction (fonction utilisant les données renvoyées du serveur) au serveur appelant de la fonction.

CallBack

 function doAjax(callbackFunc, method, url) {
  var xmlHttpReq = new XMLHttpRequest();
  xmlHttpReq.open(method, url);
  xmlHttpReq.onreadystatechange = function() {

      if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
        callbackFunc(xmlHttpReq.responseText);
      }


  }
  xmlHttpReq.send(null);

}

Dans mon code, il est appelé

function loadMyJson(categoryValue){
  if(categoryValue==="veg")
  doAjax(print,"GET","http://localhost:3004/vegetables");
  else if(categoryValue==="fruits")
  doAjax(print,"GET","http://localhost:3004/fruits");
  else 
  console.log("Data not found");
}

Lisez ici les nouvelles méthodes dans ECMA (2016/17) pour passer un appel asynchrone (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856

59
Aniket Jha

Une autre solution consiste à exécuter du code via un exécuteur séquentiel nsynjs .

Si la fonction sous-jacente est promisifiée

nsynjs évaluera toutes les promesses de manière séquentielle et placera le résultat de la promesse dans la propriété data:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

Si la fonction sous-jacente n'est pas promise

Étape 1. Enroulez la fonction avec le rappel dans l'encapsuleur nsynjs-aware (si elle a une version promise, vous pouvez ignorer cette étape):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

Étape 2. Mettez la logique synchrone en fonction:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

Étape 3. Exécutez la fonction de manière synchrone via nsynjs:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs évaluera tous les opérateurs et expressions, étape par étape, en suspendant l'exécution au cas où le résultat d'une fonction lente n'est pas prêt.

Plus d'exemples ici: https://github.com/amaksr/nsynjs/tree/master/examples

59
amaksr

C'est un problème très courant auquel nous sommes confrontés lorsque nous nous débattons avec les "mystères" de JavaScript. Laissez-moi essayer de démystifier ce mystère aujourd'hui.

Commençons par une simple fonction JavaScript:

function foo(){
// do something 
 return 'wohoo';
}

let bar = foo(); // bar is 'wohoo' here

Il s'agit d'un simple appel de fonction synchrone (où chaque ligne de code est "terminée avec son travail" avant la suivante dans l'ordre) et le résultat est identique à celui attendu.

Ajoutons maintenant un peu de torsion, en introduisant un peu de retard dans notre fonction, de sorte que toutes les lignes de code ne soient pas "finies" dans l’ordre. Ainsi, il va émuler le comportement asynchrone de la fonction:

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

Alors voilà, ce délai vient de casser la fonctionnalité que nous attendions! Mais que s'est-il passé exactement? En fait, c’est plutôt logique si vous examinez le code. la fonction foo(), lors de l'exécution, ne renvoie rien (la valeur renvoyée est donc undefined), mais elle active un temporisateur, qui exécute une fonction après 1s pour renvoyer 'wohoo'. Mais comme vous pouvez le constater, la valeur assignée à bar correspond aux éléments renvoyés immédiatement par foo (), et non à tout autre élément ultérieur.

Alors, comment abordons-nous ce problème?

Demandons à notre fonction un PROMISE. Promise, c’est vraiment ce que cela signifie: cela signifie que la fonction vous garantit de fournir le résultat final à venir. voyons donc cela en action pour notre petit problème ci-dessus:

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when execution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});

Ainsi, le résumé est - pour aborder les fonctions asynchrones telles que les appels basés sur ajax, etc., vous pouvez utiliser une promesse de resolve la valeur (que vous avez l'intention de renvoyer). Ainsi, en bref, vous résolution valeur au lieu de retourne, dans les fonctions asynchrones.

UPDATE (Promises avec async/wait)

En plus d'utiliser then/catch pour travailler avec des promesses, il existe une autre approche. L'idée est de reconnaître une fonction asynchrone puis d'attendre les promesses à résoudre, avant de passer à la ligne de code suivante. Ce n'est toujours que la promises sous le capot, mais avec une approche syntaxique différente. Pour clarifier les choses, vous pouvez trouver une comparaison ci-dessous:

version alors/catch:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
         throw err;
       })
     resolve(users);
 }

version async/wait:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        throw err;
     }
  }
52
Anish K.

ECMAScript 6 a des "générateurs" qui vous permettent de programmer facilement dans un style asynchrone.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Pour exécuter le code ci-dessus, procédez comme suit:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Si vous devez cibler des navigateurs qui ne prennent pas en charge ES6, vous pouvez exécuter le code par le biais de Babel ou de fermeture-compilateur pour générer ECMAScript 5.

Les callback ...args sont encapsulés dans un tableau et sont déstructurés lorsque vous les lisez afin que le modèle puisse gérer des rappels comportant plusieurs arguments. Par exemple avec node ​​fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
38
James

Voici quelques approches pour travailler avec des requêtes asynchrones:

  1. objet Promise du navigateur
  2. Q - Une bibliothèque de promesses pour JavaScript
  3. A + Promises.js
  4. jQuery différé
  5. API XMLHttpRequest
  6. Utilisation du concept de rappel - Comme implémentation en première réponse

Exemple: jQuery a différé l'implémentation pour fonctionner avec plusieurs requêtes

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.Push($.getJSON('request/ajax/url/1'));
      requests.Push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();
37
Mohan Dere

Réponse courte: Votre méthode foo() retourne immédiatement, tandis que l'appel $ajax() s'exécute de manière asynchrone après le retour de la fonction . Le problème est alors de savoir comment ou où stocker les résultats récupérés par l'appel asynchrone une fois qu'il est renvoyé.

Plusieurs solutions ont été données dans ce fil. Le moyen le plus simple consiste peut-être à transmettre un objet à la méthode foo() et à stocker les résultats dans un membre de cet objet une fois l'appel asynchrone terminé.

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Notez que l'appel à foo() ne retournera toujours rien d'utile. Cependant, le résultat de l'appel asynchrone sera désormais stocké dans result.response.

35
David R Tribble

Utilisez une fonction callback() à l'intérieur du succès foo(). Essayez de cette façon. C'est simple et facile à comprendre.

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
34
Mahfuzur Rahman

Nous nous trouvons dans un univers qui semble progresser dans une dimension que nous appelons "temps". Nous ne comprenons pas vraiment ce qu'est le temps, mais nous avons développé des abstractions et un vocabulaire qui permettent de raisonner et d'en parler: "passé", "présent", "futur", "avant", "après".

Les systèmes informatiques que nous construisons - de plus en plus - ont le temps comme dimension importante. Certaines choses sont prêtes pour l'avenir. Ensuite, d'autres choses doivent se produire après que ces premières choses se sont finalement produites. C'est la notion de base appelée "asynchronicité". Dans notre monde de plus en plus en réseau, le cas le plus courant d'asynchronicité attend qu'un système distant réponde à une requête.

Prenons un exemple. Vous appelez le laitier et commandez du lait. Quand ça vient, tu veux le mettre dans ton café. Vous ne pouvez pas mettre le lait dans votre café pour l'instant, car il n'est pas encore arrivé. Vous devez attendre qu'il vienne avant de le mettre dans votre café. En d'autres termes, ce qui suit ne fonctionnera pas:

var milk = order_milk();
put_in_coffee(milk);

Parce que JS n'a aucun moyen de savoir qu'il doit attendre pour que order_milk se termine avant d'exécuter put_in_coffee. En d'autres termes, il ne sait pas que order_milk est asynchrone - est quelque chose qui ne produira pas de lait avant un certain temps. JS et d'autres langages déclaratifs exécutent une instruction après l'autre sans attendre.

L’approche classique de ce problème par JS, tirant parti du fait que JS prend en charge les fonctions en tant qu’objets de première classe pouvant être échangés, consiste à transmettre une fonction en tant que paramètre à la demande asynchrone, qu’elle invoquera une fois celle-ci terminée. sa tâche dans le futur. C'est l'approche de "rappel". Cela ressemble à ceci:

order_milk(put_in_coffee);

order_milk démarre, commande le lait, puis, quand et seulement quand il arrive, il appelle put_in_coffee.

Le problème avec cette approche de rappel est qu’elle pollue la sémantique normale d’une fonction rapportant son résultat avec return; à la place, les fonctions ne doivent pas rapporter leurs résultats en appelant un rappel donné en tant que paramètre. En outre, cette approche peut rapidement devenir lourde s’agissant de longues séquences d’événements. Par exemple, supposons que je veuille attendre que le lait soit versé dans le café, puis seulement à ce moment-là, effectue une troisième étape, à savoir boire le café. Je finis par avoir besoin d'écrire quelque chose comme ça:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

où je passe à put_in_coffee à la fois le lait à mettre dedans, ainsi que l'action (drink_coffee) à exécuter une fois que le lait a été mis en place. Ce code devient difficile à écrire, à lire, et déboguer.

Dans ce cas, nous pourrions réécrire le code dans la question comme suit:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Entrer des promesses

Ce fut la motivation de la notion de "promesse", qui est un type particulier de valeur qui représente un résultat futur ou asynchrone. Cela peut représenter quelque chose qui est déjà arrivé ou qui va arriver dans le futur ou peut ne jamais arriver du tout. Les promesses ont une seule méthode, nommée then, à laquelle vous transmettez une action à exécuter lorsque le résultat représenté par la promesse a été réalisé.

Dans le cas de notre lait et de notre café, nous concevons order_milk pour renvoyer une promesse pour le lait qui arrive, puis spécifions put_in_coffee en tant qu'action then, comme suit:

order_milk() . then(put_in_coffee)

Un avantage de ceci est que nous pouvons les chaîner ensemble pour créer des séquences d'occurrences futures ("chaînage"):

order_milk() . then(put_in_coffee) . then(drink_coffee)

Appliquons des promesses à votre problème particulier. Nous allons envelopper notre logique de requête dans une fonction, qui retourne une promesse:

function get_data() {
  return $.ajax('/foo.json');
}

En fait, tout ce que nous avons fait est d’ajouter un return à l’appel de $.ajax. Cela fonctionne parce que $.ajax de jQuery retourne déjà une sorte de chose qui ressemble à une promesse. (En pratique, sans entrer dans les détails, nous préférerions encapsuler cet appel afin de renvoyer une vraie promesse, ou utiliser une alternative à $.ajax qui le fait.) Maintenant, si nous voulons charger le fichier et attendre pour qu’il finisse et fasse quelque chose, on peut simplement dire

get_data() . then(do_something)

par exemple,

get_data() . 
  then(function(data) { console.log(data); });

Lorsque nous utilisons des promesses, nous finissons par passer beaucoup de fonctions dans then, il est donc souvent utile d'utiliser les fonctions de flèche plus compactes de style ES6:

get_data() . 
  then(data => console.log(data));

Le mot clé async

Mais il y a toujours quelque chose de vaguement insatisfaisant à l'idée de devoir écrire le code d'une manière si synchrone et d'une manière très différente si asynchrone. Pour synchrone, on écrit

a();
b();

mais si a est asynchrone, avec des promesses nous devons écrire

a() . then(b);

Ci-dessus, nous avons dit: "JS n'a aucun moyen de savoir qu'il doit attendre pour que le premier appel se termine avant d'exécuter le second". Ce ne serait pas bien s'il y avait était un moyen de le dire à JS? Il s’avère qu’il existe - le mot clé await, utilisé dans un type spécial de fonction appelé fonction "asynchrone". Cette fonctionnalité fait partie de la prochaine version d'ES, mais est déjà disponible dans les transpilers tels que Babel, avec les bons presets. Cela nous permet d'écrire simplement

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

Dans votre cas, vous pourriez écrire quelque chose comme

async function foo() {
  data = await get_data();
  console.log(data);
}
32
user663031

Bien sûr, il existe de nombreuses approches telles que la demande synchrone, c'est prometteur, mais de mon expérience, je pense que vous devriez utiliser l'approche de rappel. C'est naturel au comportement asynchrone de Javascript. Ainsi, votre extrait de code peut être réécrit un peu différemment:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}
27
Khoa Bui

La question était:

Comment renvoyer la réponse d'un appel asynchrone?

qui PEUVENT être interprétés comme:

Comment rendre le code asynchrone synchrone ?

La solution consiste à éviter les rappels et à utiliser une combinaison de promesses et de async/wait .

Je voudrais donner un exemple pour une demande Ajax.

(Bien qu'il puisse être écrit en Javascript, je préfère l'écrire en Python et le compiler en Javascript en utilisant Transcrypt . Ce sera assez clair.)

Permet d’abord d’utiliser JQuery pour que $ soit disponible en tant que S:

__pragma__ ('alias', 'S', '$')

Définissez une fonction qui retourne une promesse , dans ce cas un appel Ajax:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Utilisez le code asynchrone comme s'il était synchrone :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
26

Utiliser la promesse

La réponse la plus parfaite à cette question utilise Promise.

function ajax(method, url, params) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open(method, url);
    xhr.send(params);
  });
}

Usage

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

Mais attendez...!

Il y a un problème avec l'utilisation des promesses!

Pourquoi devrions-nous utiliser notre propre promesse personnalisée?

J'utilisais cette solution pendant un moment jusqu'à ce que je découvre une erreur dans les anciens navigateurs:

Uncaught ReferenceError: Promise is not defined

J'ai donc décidé d'implémenter ma propre classe Promise pour ES3 à ci-dessous compilateurs js si ce n'est pas défini. Ajoutez simplement ce code avant votre code principal, puis utilisez Promise en toute sécurité!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.Push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.Push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
20
Amir Forsati

Plutôt que de vous envoyer du code, deux concepts sont essentiels pour comprendre comment JS gère les rappels et l’asynchronicité. (Est-ce que c'est un mot?)

modèle de boucle d'événement et de concurrence

Il y a trois choses dont vous devez être conscient; La file d'attente; la boucle d'événement et la pile

En termes généraux et simplistes, la boucle d'événement est comme le chef de projet, elle écoute en permanence toutes les fonctions qui veulent s'exécuter et communique entre la file d'attente et la pile.

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Une fois qu'il a reçu un message, il l'ajoute à la file d'attente. La file d'attente est la liste des tâches en attente d'exécution (comme votre demande AJAX.). imaginez ça comme ça:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

Lorsque l'un de ces messages est sur le point de s'exécuter, il apparaît dans la file d'attente et crée une pile. Cette pile correspond à tout ce que JS doit exécuter pour exécuter l'instruction contenue dans le message. Donc, dans notre exemple, il est dit d'appeler foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

Donc, tout ce que foobarFunc doit exécuter (dans notre cas, anotherFunction) sera propulsé dans la pile. exécuté, puis oublié - la boucle d'événements passera ensuite à l'élément suivant de la file d'attente (ou écoutera les messages)

L'essentiel ici est l'ordre d'exécution. C'est

QUAND est quelque chose qui va fonctionner

Lorsque vous appelez AJAX vers un tiers externe ou exécutez un code asynchrone (par exemple, setTimeout), JavaScript dépend d'une réponse avant de pouvoir continuer.

La grande question est quand aura-t-il la réponse? La réponse est que nous ne le savons pas - la boucle d’événement attend donc que le message "Hé me lance". Si JS attendait ce message de manière synchrone, votre application se figerait et serait nul. JS continue donc d'exécuter le prochain élément de la file d'attente en attendant que le message soit ajouté à la file d'attente.

C'est pourquoi avec la fonctionnalité asynchrone, nous utilisons des choses appelées callbacks . C'est un peu comme un promesse littéralement. Comme dans I promesse de retourner quelque chose à un moment donné jQuery utilise des rappels spécifiques appelés deffered.donedeffered.fail et deffered.always (entre autres). Vous pouvez les voir tous ici

Donc, ce que vous devez faire est de transmettre une fonction qui devrait être exécutée à un moment donné avec les données qui lui sont transmises.

Parce qu'un rappel n'est pas exécuté immédiatement mais plus tard, il est important de transmettre la référence à la fonction non exécutée. alors

function foo(bla) {
  console.log(bla)
}

donc la plupart du temps (mais pas toujours), vous passerez foo pas foo()

Espérons que cela aura du sens. Lorsque vous rencontrez des problèmes de ce type qui semblent déroutants - je vous recommande vivement de lire la documentation au complet pour au moins bien la comprendre. Cela fera de vous un meilleur développeur.

16
Matthew Brent

En utilisant ES2017, vous devriez avoir ceci comme déclaration de fonction

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

Et l'exécuter comme ça.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

Ou la syntaxe Promise

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})
15
Fernando Carvajal

Voyons d'abord la forêt avant de regarder les arbres.

Il y a beaucoup de réponses informatives avec beaucoup de détails ici, je ne vais pas les répéter. La clé de la programmation en JavaScript est d’avoir d’abord le modèle mental correct de l’exécution générale.

  1. Votre point d'entrée est exécuté à la suite d'un événement. Par exemple, une balise de script avec du code est chargée dans le navigateur. (En conséquence, c’est pourquoi vous devrez peut-être vous préoccuper de la capacité de la page à exécuter votre code si elle nécessite la construction préalable d’éléments dom, etc.)
  2. Votre code s'exécute jusqu'à la fin - quel que soit le nombre d'appels asynchrones qu'il effectue - sans exécuter aucun de vos rappels, y compris les demandes XHR, les délais impartis, les gestionnaires d'événement dom, etc. Chacun de ces rappels en attente d'exécution resteront dans une file d'attente, attendant leur tour d'être exécutés après l'exécution des autres événements déclenchés.
  3. Chaque rappel individuel à une demande XHR, à l'expiration ou à la clôture de l'événement une fois invoqué, sera ensuite exécuté jusqu'à son terme.

La bonne nouvelle est que si vous comprenez bien ce point, vous ne devrez jamais vous inquiéter des conditions de course. Vous devez avant tout définir la manière dont vous souhaitez organiser votre code comme étant essentiellement la réponse à différents événements discrets, et la manière dont vous souhaitez les relier dans une séquence logique. Vous pouvez utiliser des promesses ou de nouvelles versions asynchrones/wait de niveau supérieur comme outils à cette fin, ou vous pouvez créer vos propres.

Mais vous ne devriez utiliser aucun outil tactique pour résoudre un problème tant que vous n'êtes pas à l'aise avec le domaine du problème actuel. Dessinez une carte de ces dépendances pour savoir ce qui doit être exécuté quand. Tenter une approche ad hoc de tous ces rappels ne vous servira à rien.

14
Haim Zamir

Voici un exemple qui fonctionne:

const validateName = async userName => {
  const url = "abc/xyz";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
10
Alex Montoya

Request fonctionne de manière ansynchrone, de sorte que vous ne pouvez pas lire les données de manière synchrone en tant que code typique. Cependant, en utilisant async/await, vous pouvez créer un code asynchrone qui ressemble/ressemble beaucoup au code de synchronisation. Code dont les données de demande de processus doivent être enveloppées par une fonction asynchrone (load dans l'extrait ci-dessous) et à l'intérieur de laquelle vous devez ajouter await keywort avant foo() (qui utilise également async/await ).

async function foo() {
  var url= 'https://jsonplaceholder.typicode.com/todos/1';
  var result= await (await fetch(url)).text(); // or .json()
  return result;
}

async function load() {
  var result = await foo();
  console.log(result);
}

load();
5
Kamil Kiełczewski

utilisation de async/await avec un transpileur tel que Babel pour le faire fonctionner dans des navigateurs plus anciens. Vous devrez également installer ce paramètre prédéfini et polyfill de Babel à partir de npm: npm i -D babel-preset-env babel-polyfill.

function getData(ajaxurl) { 
  return $.ajax({
    url: ajaxurl,
    type: 'GET',
  });
};

async test() {
  try {
    const res = await getData('https://api.icndb.com/jokes/random')
    console.log(res)
  } catch(err) {
    console.log(err);
  }
}

test();

ou le rappel .then n'est qu'un autre moyen d'écrire la même logique.

getData(ajaxurl).then(function(res) {
    console.log(res)
}
1
Murtaza Hussain