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

j'ai une fonction foo qui fait une requête Ajax. Comment puis-je retourner la réponse de foo ?

j'ai essayé de retourner la valeur de l'appel success ainsi que d'assigner la réponse à une variable locale à l'intérieur de la fonction et de retourner celle-ci, mais aucune de ces façons ne renvoie réellement 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`.
4470
demandé sur Ry- 2013-01-08 21:06:14

30 réponses

→ pour une explication plus générale du comportement asynchrone avec différents exemples, voir Pourquoi ma variable n'est-elle pas modifiée après que je l'ai modifiée à 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 Un dans Ajax signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt de la réception de la réponse) est retiré du flux d'exécution normale. Dans votre exemple, $.ajax retourne immédiatement et la déclaration suivante, return result; , est exécutée avant même que la fonction que vous avez passée comme success ait été appelée.

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

synchrone

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

la même chose se produit lorsque vous faites un appel de fonction contenant du code "normal" :

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

var item = findItem();

// Do something with item
doSomethingElse();

même si l'exécution de findItem peut prendre beaucoup de temps, tout code venant après var item = findItem(); doit attendre jusqu'à ce que la fonction renvoie le résultat.

asynchrone

vous appelez votre ami à nouveau pour la même raison. Mais cette fois, vous lui dites que vous êtes pressé et il devrait vous rappeler sur votre téléphone mobile. Vous raccrochez, quitter la maison et faire 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 quand 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 la déclaration après L'appel Ajax est exécutée. Pour obtenir la réponse éventuellement, vous fournissez une fonction à appeler une fois que la réponse a été reçue, un callback (vous avez remarqué quelque chose? rappeler ?). Toute instruction venant après cet appel est exécutée avant que le callback soit appelé.


Solution(s)

Embrassez la nature asynchrone de JavaScript! bien que certaines opérations asynchrones fournissent des contreparties synchrones (comme "Ajax"), il est généralement déconseillé de les utiliser, en particulier dans un navigateur cadre.

Pourquoi est-ce mauvais demandez-vous?

JavaScript s'exécute dans le thread de L'interface utilisateur du navigateur et tout processus de longue durée va verrouiller l'interface utilisateur, ce qui le rend inactive. En outre, il y a une limite supérieure sur le temps d'exécution pour JavaScript et le navigateur demandera à l'utilisateur de continuer l'exécution ou non.

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

dans ce qui suit, Nous allons examiner trois solutions différentes qui sont tous construits l'un sur l'autre:

  • Promesses async/await (ES2017+, disponible dans les navigateurs plus anciens si vous utilisez un transpiler ou régénérateur)
  • Rappels (populaire dans le nœud)
  • Promesses then() (ES2015+, disponible dans les navigateurs plus anciens si vous utilisez l'un des nombreux promesse bibliothèques)

tous les trois sont disponibles dans les navigateurs courants, et noeud 7+.


ES2017+: promesses avec async/await

la version ECMAScript sortie en 2017 introduit syntaxe de support de niveau pour asynchrones fonctions. Avec l'aide de async et await , vous pouvez écrire asynchrone dans un "synchrone". Le code est toujours asynchrone, mais il est plus facile à lire/comprendre.

async/await s'appuie sur des promesses: une fonction async renvoie toujours une promesse. await "déballe" une promesse et résulte dans la valeur que la promesse a été résolu avec ou jette une erreur si le la promesse a été rejetée.

Important: vous ne pouvez utiliser await à l'intérieur d'une fonction async . Cela signifie qu'au niveau le plus élevé, vous devez encore travailler directement avec la promesse.

vous pouvez en savoir plus sur async 1519600920" et await sur MDN.

voici un exemple qui s'ajoute au retard 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;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

Actuel navigateur et nœud prise en charge des versions async/await . Vous pouvez également prendre en charge des environnements plus anciens en transformant votre code en ES5 à l'aide de regenerator (ou des outils qui utilisent regenerator, tels que Babel ).


Laissez fonctions accepter rappels

Un rappel est simplement passé à une autre fonction. Cette autre fonction peut appeler la fonction passée 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 effectué. Habituellement, le résultat est transmis au rappel.

Dans l'exemple de la question, vous pouvez faire foo accepter un rappel et l'utiliser comme success de rappel. Donc ce

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 elle-même est définie comme suit:

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

callback se réfère à la fonction que nous passons à foo quand nous l'appelons et nous le transmettons simplement à success . C'est-à-dire: une fois la demande Ajax acceptée, $.ajax appellera callback et transmettra la réponse à la callback (qui peut être référencé avec result , puisque c'est ainsi que nous avons défini le callback).

vous pouvez également traiter la réponse avant de la passer à l'appel:

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

il est plus facile d'écrire du code en utilisant des callbacks qu'il n'y paraît. Après tout, JavaScript dans le navigateur est fortement event-driven (DOM events). Recevoir la réponse Ajax n'est rien d'autre qu'un événement.

Des difficultés peuvent surgir lorsque vous doivent travailler avec le code de tiers, mais la plupart des problèmes peuvent être résolus en pensant simplement à travers le flux d'application.


ES2015+: des Promesses de alors()

le Promise API est une nouvelle fonctionnalité D'ECMAScript 6 (ES2015), mais il a déjà bon navigateur prise en charge . Il existe également de nombreuses bibliothèques qui implémentent l'API Standard Promises et fournir des méthodes supplémentaires pour faciliter l'utilisation et la composition des fonctions asynchrones (p. ex. oiseau bleu ).

les Promesses sont des conteneurs pour avenir valeurs". Lorsque la promesse reçoit la valeur (elle est résolue ) ou lorsqu'elle est annulée ( rejetée ), elle en avertit tous ses "auditeurs" qui veulent accéder à cette valeur.

l'avantage sur la plaine rappels est qu'ils vous permettent de découpler le code, et 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 ceci:

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écrivant tous les avantages que la promesse offre est au-delà de la portée de cette réponse, mais si vous écrivez un nouveau code, vous devriez sérieusement les envisager. Ils fournissent une grande abstraction et la séparation de votre code.

plus d'informations sur les promesses: HTML5 rocks-JavaScript promesses

note de Côté: jQuery différée des objets

Deferred objects sont la mise en œuvre personnalisée des promesses de 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 Ajax méthode de jQuery renvoie déjà un "objet différé" (en fait une promesse d'un objet différé) que vous pouvez simplement retourner à partir de votre fonction:

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

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

note de Côté: la Promesse de pièges

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 questions asynchrones ci-dessus. Plus précisément, $.ajax() ne bloque pas le code pendant qu'il vérifie la page '/password' sur votre serveur - il envoie une requête au serveur et pendant qu'il attend, retourne immédiatement un objet Ajax de jQuery différé, pas la réponse du serveur. Cela signifie que la déclaration if va toujours obtenir cet objet différé, le traiter comme true , et procéder comme si l'utilisateur est connecté. Pas bonne.

mais le fix 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 synchrones "Ajax

comme je l'ai mentionné, certains(!) les opérations asynchrones ont des contreparties synchrones. Je ne recommande pas leur utilisation, mais par souci d'exhaustivité, voici comment vous exécuteriez un appel synchrone:

sans jQuery

Si vous utilisez directement un XMLHTTPRequest objet, passer false comme troisième argument pour .open .

jQuery

si vous utilisez jQuery , vous pouvez définir l'option async à false . Notez que cette option est dépréciée depuis jQuery 1.8. Vous pouvez alors soit encore utiliser un success callback 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 jQuery Ajax, telle que $.get , $.getJSON , etc., vous devez le changer en $.ajax (puisque vous ne pouvez passer les paramètres de configuration qu'à $.ajax ).

Heads up! il n'est pas possible de faire une requête synchrone JSONP . De par sa nature même, JSONP est toujours asynchrone (une raison de plus de ne même pas envisager cette option).

4799
répondu Felix Kling 2018-10-11 11:07:41

si vous êtes et non en utilisant jQuery dans votre code, cette réponse est pour vous

votre code devrait être quelque chose du genre:

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 fait un bon travail en écrivant une réponse pour les gens qui utilisent jQuery pour AJAX, j'ai décidé de fournir une alternative pour les gens qui ne le sont pas.

( Note, pour ceux qui utilisent la nouvelle API fetch , Angular ou promises j'ai ajouté une autre réponse sous )


Ce que vous êtes confronté à

ceci est un résumé de" L'explication du problème " de l'autre réponse, si vous n'êtes pas sûr après avoir lu ceci, lire cela.

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é de la norme le flux d'exécution. Dans votre exemple, .send retourne immédiatement et l'instruction suivante, return result; , est exécutée avant même que la fonction que vous avez passée en tant que success ait été appelée.

cela signifie que lorsque vous retournez, l'auditeur que vous avez défini n'a pas encore exécuté, ce qui signifie que la valeur que vous retournez n'a pas été définie.

Voici une simple analogie

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

(violon)

la valeur de a retournée est undefined puisque la partie a=5 n'a pas encore exécuté. AJAX agit comme ceci, vous retournez la valeur avant que le serveur ait eu la chance de dire à votre navigateur Quelle est cette valeur.

une solution possible à ce problème est de coder de façon réactive , en indiquant à votre programme ce qu'il faut 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);
}

cela s'appelle CPS . Fondamentalement, nous passons getFive une action à effectuer quand il termine, nous disons à notre code comment réagir quand un événement termine (comme notre appel AJAX, ou dans ce cas le temps mort).

Usage serait:

getFive(onComplete);

qui devrait alerter" 5 " à l'écran. (violon) .

solutions possibles

il y a essentiellement deux façons de résoudre ce problème:

  1. synchronisez L'appel AJAX (appelons-le SJAX).
  2. restructurer votre code pour fonctionner correctement avec les callbacks.

1. AJAX synchrone - ne le faites pas!!

Comme pour AJAX synchrone, ne fais pas ça! la réponse de Félix soulève des arguments convaincants sur la raison pour laquelle c'est une mauvaise idée. En somme up, il va geler le navigateur de l'utilisateur jusqu'à ce que le serveur renvoie la réponse et créer une très mauvaise expérience utilisateur. Voici un autre court résumé tiré de MDN sur pourquoi:

XMLHttpRequest supporte les communications synchrones et asynchrones. En général, cependant, les requêtes asynchrones devraient être préférées aux requêtes synchrones pour des raisons de performance.

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

si vous avez pour 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 de code foo peut être fait pour accepter un rappel. Nous allons dire à notre code comment réagir quand foo sera terminé.

:

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, ce qui le fait ressembler à:

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

pour plus de détails sur la façon dont ce type de conception de rappel est fait, 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 fait que notre fonction foo accepte une action à exécuter lorsque L'AJAX est terminé avec succès, nous pouvons étendre cela en vérifiant si l'état de réponse n'est pas 200 et en agissant en conséquence (créer un gestionnaire de défaillance et tel). Résoudre efficacement notre problème.

si vous avez encore du mal à comprendre ce lisez le guide de démarrage AJAX à MDN.

910
répondu Benjamin Gruenbaum 2018-03-01 01:03:52

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

si vous n'utilisez pas jQuery et que vous voulez un court XMLHttpRequest 2 qui fonctionne sur les navigateurs modernes et aussi sur les navigateurs mobiles, je vous 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 énumérées.
  2. le rappel est réglé directement (donc pas de fermetures inutiles supplémentaires).
  3. il utilise la nouvelle onload (de sorte que vous n'avez pas à vérifier pour readystate && statut)
  4. il y a d'autres situations dont je ne me souviens pas qui rendent la XMLHttpRequest 1 ennuyeuse.

il y a deux façons d'obtenir la réponse de cet appel Ajax (trois utilisant le nom XMLHttpRequest var):

le le plus simple:

this.response

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

e.target.response

exemple:

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

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

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

rien de plus facile.

maintenant, certaines personnes vont probablement dire qu'il est préférable d'utiliser onreadystatechange ou même la variable XMLHttpRequest nom. C'est faux.

Check out XMLHttpRequest advanced features

il a supporté sur 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 est seulement utile si vous voulez obtenir les en-têtes sur l'état 2.

utilisant le nom de la variable XMLHttpRequest est une autre grosse erreur puisque vous devez exécuter le callback à 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 ... c'est une fonction très courte, mais il ne get & post.

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 passer un formulaire complet élément ( document.getElementsByTagName('form')[0] ):

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

Ou de définir certaines valeurs personnalisées:

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

comme vous pouvez le voir, 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 brise complètement le point de la réponse. Ce qui est une bonne façon courte D'utiliser Ajax dans le bon façon?

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'erreurs qui est défini statiquement de sorte qu'il ne compromette pas la fonction. Le gestionnaire d'erreur peuvent être utilisés pour d'autres fonctions.

mais pour vraiment obtenir une erreur le seulement façon est d'écrire une mauvaise URL dans lequel cas chaque navigateur jette une erreur.

Erreur

les gestionnaires sont peut-être utiles si vous définissez des en-têtes personnalisés, définissez le type de responsabilité au tampon de tableau de blob ou n'importe quoi d'autre....

même si vous passez 'POSTAPAP' comme méthode, il ne lancera pas d'erreur.

même si vous passez 'fdggdgilfdghfldj' comme formdata il ne lancera pas d'erreur.

dans le premier cas, l'erreur se trouve à l'intérieur du displayAjax() sous this.statusText comme Method not Allowed .

dans le second cas, il suffit travail. Vous devez vérifier du côté du serveur si vous avez transmis les bonnes données post.

cross-domain non autorisé lance l'erreur automatiquement.

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

il n'y a que le this.type qui est défini à erreur.

Pourquoi ajouter un gestionnaire d'erreur si vous n'avez aucun contrôle sur les erreurs? La plupart des erreurs sont retournées dans la fonction callback. displayAjax() .

donc: pas besoin de vérifications d'erreurs si vous êtes capable de copier et coller l'URL correctement. ;)

PS: comme le premier test, j'ai écrit x('x', displayAjax)... et il est totalement obtenu une réponse...??? J'ai donc vérifié le dossier où se trouve le HTML, et il y avait un fichier appelé 'x.xml". Donc, même si vous oubliez l'extension de votre fichier XMLHttpRequest 2 le trouvera . I LOL'd


lire un fichier synchrone

ne faites pas ça.

si vous voulez bloquer le navigateur pendant un certain temps charger un gros fichier txt sympa synchrone.

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

Maintenant vous pouvez le faire

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

il n'y a pas d'autre moyen de le faire de façon non asynchrone. (Oui, avec la boucle setTimeout... mais sérieusement?)

un autre point est... si vous travaillez avec APIs ou si vous possédez les fichiers list ou n'importe quoi, 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 quoi que vous ayez besoin 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 un usage 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 GET only...

mais vous pouvez faire beaucoup de choses avec XMLHttpRequest 2:

j'ai fait un gestionnaire de téléchargement (en utilisant des gammes sur les deux côtés avec CV, filereader, système de Fichiers), divers convertisseurs de redimensionnement d'image en utilisant canvas, peuplez les bases de données websql avec base64images et bien plus encore... Mais dans ces cas, vous devez créer une fonction uniquement dans ce but... parfois, vous avez besoin d'un blob, des tampons de tableau, vous pouvez définir des en-têtes, outrepasser mimetype et il ya beaucoup plus...

mais la question ici est de savoir comment retourner une réponse Ajax... (J'ai ajouté un moyen facile.)

315
répondu cocco 2017-10-18 09:22:56

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

cela signifie AngularJS, jQuery (avec différé), remplacement de XHR natif (fetch), EmberJS, sauvegarde de BackboneJS ou toute bibliothèque de noeuds qui renvoie des promesses.

votre code devrait être quelque chose du genre:

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 fait un bon travail en écrivant une réponse pour les gens utilisant jQuery avec des rappels pour AJAX. J'ai une réponse pour XHR natif. Cette réponse est pour l'usage générique des promesses soit sur le frontend ou le backend.


La question centrale

le modèle de concurrence 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 manipulateurs then sont toujours exécutée de manière asynchrone, après le code ci-dessous d'eux qui n'est pas dans un .then gestionnaire.

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

Voici une analogie simple pour la question:

    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écuter en une seconde, mais en ce moment c'est pas pertinente pour la valeur retournée.

puisque l'opération n'a pas encore eu lieu (AJAX, appel serveur, IO, minuterie) vous retournez la valeur avant que la requête ait la chance de dire à votre code ce qu'est cette valeur.

une solution possible à ce problème est de coder de manière réactive , dire à votre programme ce qu'il doit faire lorsque le calcul est terminé. Les promesses permettent activement ceci en étant de nature temporelle (sensible au temps).

récapitulatif Rapide de promesses

une Promesse est Une de la valeur au fil du temps . Les promesses ont état, elles commencent comme en attente sans valeur et peuvent se régler à:

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

une promesse ne peut changer que les États une fois après quoi elle restera toujours dans le même état pour toujours. Vous pouvez attacher les handlers then à des promesses pour extraire leur valeur et gérer les erreurs. then gestionnaires de permettre de chaînage des appels. Les promesses sont créées par en utilisant des API qui retournent les . Par exemple, le remplacement plus moderne D'AJAX fetch ou les promesses de retour de jQuery $.get .

Quand nous appelons .then sur une promesse et retour quelque chose, nous obtenons une promesse pour la transformation de valeur . Si nous rendons une autre promesse, nous obtiendrons des choses étonnantes, mais retenons nos chevaux.

avec des promesses

voyons comment on peut résolvez la question ci-dessus avec des promesses. Tout d'abord, démontrons notre compréhension des états de promesse d'en haut en utilisant le constructeur de promesse 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 que nous avons converti setTimeout pour utiliser des 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 retourner un valeur que nous ne peut pas faire à cause du modèle de concurrence - nous retournons un wrapper pour une valeur que nous pouvons unwrap avec then . C'est comme une boîte qu'on peut ouvrir avec then .

l'Application de cette

c'est la même chose pour votre appel 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 retourner des valeurs provenant d'appels asynchrones. mais nous pouvons utiliser des promesses et les enchaîner pour effectuer le traitement. Nous savons maintenant comment retourner la réponse d'un appel asynchrone.

ES2015 (ES6)

ES6 introduit generators qui sont des fonctions qui peuvent revenir au milieu et ensuite reprendre le point où ils étaient. Cela 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. Tout cela est intéressant et ouvre la place pour beaucoup de possibilité, il est un cas intéressant.

si la séquence que nous produisons est une séquence d'actions plutôt que de nombres - nous pouvons mettre en pause la fonction chaque fois qu'une action est cédée et l'attendre avant de reprendre la fonction. Donc au lieu d'une séquence de numéros, nous avons besoin d'une séquence de avenir valeurs: des 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 font cela pour vous, l'écriture d'un est quelques courtes lignes de code, mais est au-delà de la portée de cette réponse. Je vais utiliser le Promise.coroutine de Bluebird ici, mais il y a d'autres emballages 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 rend une promesse elle-même, que nous peut 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, c'est encore plus standardisé, il ya plusieurs propositions en ce moment, mais dans chacun d'eux, vous pouvez await promesse. Il s'agit simplement de "sugar" (syntaxe plus agréable) pour la proposition ES6 ci-dessus en ajoutant les mots-clés async et await . Faisant 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 tout de même :)

253
répondu Benjamin Gruenbaum 2017-05-23 12:34:51

vous utilisez Ajax incorrectement. L'idée n'est pas de le faire renvoyer quoi que ce soit, mais de transférer les données à quelque chose appelé une fonction de rappel, qui gère les données.

C'est-à-dire:

function handleData( responseData ) {

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

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

retourner quoi que ce soit dans le gestionnaire de soumission ne fera rien. Vous devez à la place soit transmettre les données, ou faire ce que vous voulez avec elle directement à l'intérieur de la fonction de succès.

199
répondu Nic 2015-11-21 14:07:03

la solution la plus simple est de créer une fonction JavaScript et de 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);    
}); 
190
répondu Hemant Bavle 2017-01-15 06:17:51

je vous répondrai avec une horrible BD dessinée à la main. La deuxième image est la raison pour laquelle result est undefined dans votre exemple de code.

enter image description here

161
répondu Johannes Fahrenkrug 2016-08-11 17:12:24

Angular1

pour les personnes qui utilisent AngularJS , peut gérer cette situation en utilisant Promises .

Ici il dit,

promet peut être utilisé pour des fonctions asynchrones unnest et permet de enchaîner plusieurs fonctions ensemble.

vous pouvez trouver une belle 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 un coup d'oeil à l'exemple suivant, mais son 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 que de cette manière,

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 natif ES6 promet , si vous voulez l'utiliser, vous pourriez avoir besoin d'un plugin pour cela.

en outre voici les promesses spec définir ici.

118
répondu Maleen Abewardana 2017-07-06 04:45:28

la plupart des réponses ici donnent des suggestions utiles pour quand vous avez une seule opération asynchrone, mais parfois, cela apparaît quand vous avez besoin de faire une opération asynchrone pour chaque entrée dans un tableau ou d'autres structures de type liste. La tentation est de le faire:

// 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 qui ne fonctionne pas est que les rappels de doSomethingAsync n'ont pas encore couru au moment où vous essayez d'utiliser les résultats.

donc, si vous avez un tableau (ou une liste quelconque) et que vous voulez faire des opérations asynchrones pour chaque entrée, vous avez deux options: faire les opérations en parallèle (chevauchement), ou en série (l'une après l'autre dans l'ordre).

parallèle

vous pouvez démarrer chacun d'eux et garder une trace du nombre de rappels que vous attendez, et puis utiliser les résultats lorsque vous avez

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

(nous pourrions supprimer expecting et juste utiliser results.length === theArray.length , mais cela nous laisse ouvert à la possibilité que theArray est changé pendant que les appels sont en suspens...)

notez comment nous utilisons le index de forEach pour sauvegarder le résultat dans results dans la même position que l'entrée à laquelle il se rapporte, même si les résultats arrivent hors de l'ordre (puisque les appels asynchrones ne sont pas nécessairement complets dans l'ordre dans lequel ils ont été lancés).

mais que faire si vous avez besoin de retourner ces résultats d'une fonction? Comme les autres réponses l'ont fait remarquer, vous ne pouvez pas; vous devez faire en sorte que votre fonction accepte et rappelle (ou renvoie une 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 retour d'un Promise au lieu de:

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 donnait des erreurs, nous utiliserions reject pour rejeter la promesse quand nous avons eu 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 faire un emballage pour doSomethingAsync qui retourne une promesse, et puis faire le ci-dessous...)

si doSomethingAsync vous donne un promesse , vous pouvez utiliser Promise.all :

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

exemple:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
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 ils sont tous résolus, ou rejette sa promesse lors de la première des promesses que vous donnez il rejette.

série

supposez que vous ne voulez pas que les opérations soient en parallèle? Si vous souhaitez exécuter l'un après l'autre, vous avez besoin d'attendre pour chaque opération à réaliser avant de commencer la suivante. Voici un exemple d'une fonction qui le fait et appelle une callback 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 faisons le travail en série, nous pouvons simplement utiliser results.push(result) car nous savons que nous n'obtiendrons pas de résultats hors de l'ordre. 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, construire un wrapper pour doSomethingAsync qui vous donne une promesse et faire le ci-dessous...)

si doSomethingAsync vous donne une promesse, Si vous pouvez utiliser la syntaxe ES2017+ (peut-être avec un transpirateur comme Babel ), vous pouvez utiliser un async fonction 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 utiliser la syntaxe ES2017+ (encore), vous pouvez utiliser une variation sur le "promesse réduire" pattern (c'est plus complexe que la promesse habituelle réduire parce que nous ne passons pas le résultat de l'un dans le prochain, 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 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;
}
94
répondu T.J. Crowder 2017-09-13 04:34:11

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 voir getJoke est retour d'un résolu promesse (il est résolu en retournant res.data.value ). Donc, vous attendez jusqu'au $http.la requête get est terminée, puis la console .log(res.blague) est exécutée (normal asynchrone de flux).

This est le plnkr:

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

77
répondu Francisco Carmona 2017-01-13 03:55:15

une autre approche pour retourner une valeur d'une fonction asynchrone, est de passer dans un objet qui stockera le résultat de la fonction asynchrone.

voici un exemple de la même chose:

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 souvent cette approche. Je serais intéressé pour savoir comment cette approche fonctionne lorsque le câblage le résultat à travers les modules consécutifs est impliqué.

71
répondu jsbisht 2016-12-17 12:55:25

C'est l'un des endroits que liaison de données à deux voies qui est utilisé dans de nombreux cadres JavaScript nouveau fonctionnera grandement pour vous...

donc si vous utilisez angulaire, réagir ou tout autre cadre qui font liaison de données à deux voies, cette question est simplement fixe pour vous, donc en termes simples, votre résultat est undefined à la première étape, donc vous avez obtenu result = undefined avant de recevoir le données, puis dès que vous obtenez le résultat, il sera mis à jour et obtenir assigné à la nouvelle valeur qui est répondre de votre appel Ajax...

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

Vous pouvez utiliser un rappel , promesse et récemment observables pour gérer cela pour vous, par exemple dans les promesses, nous avons une fonction comme success() ou then() qui sera exécutée lorsque vos données seront prêtes pour vous, de même avec la fonction callback ou subscribe on observable .

par exemple dans votre cas que 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'information Etude sur promesses et observables qui sont de nouvelles façons de le faire async stuffs.

64
répondu Alireza 2018-05-25 13:46:01

alors que les promesses et les rappels fonctionnent bien dans de nombreuses situations, c'est une douleur à l'arrière d'exprimer quelque chose comme:

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

vous finiriez par passer par async1 ; vérifiez si name n'est pas défini ou non et appelez le callback en conséquence.

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

async1(name, async2)

alors qu'il est d'accord dans de petits exemples, il devient ennuyeux quand vous avez beaucoup de cas similaires et la manipulation des erreurs impliquées.

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 consulter le projet ici .

62
répondu rohithpr 2016-05-09 13:02:40

la réponse courte est, vous devez mettre en œuvre 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
});
59
répondu Pablo Matias Gomez 2016-08-09 18:32:41

L'exemple suivant, que j'ai écrit montre comment

  • Gérer les appels HTTP asynchrones;
  • Attendre pour la réponse de chaque appel d'API;
  • Utiliser Promesse ;
  • Utilisez Promesse.Tous les modèle de rejoindre plusieurs HTTP appels;

cet exemple pratique est autonome. Il définira un objet de requête simple qui utilise fenêtre XMLHttpRequest objet pour faire des appels. Il définira une fonction simple pour attendre qu'un tas de promesses soit accompli.

Context. L'exemple consiste à interroger le paramètre de L'API Web Spotify afin de rechercher les 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 tirera un bloc - ExecutionBlock , analyser le résultat, programmer une nouvelle série de promesses basées sur le résultat array, c'est-à-dire une liste D'objets Spotify user et exécute le nouvel appel HTTP dans le ExecutionProfileBlock de manière asynchrone.

vous pouvez alors voir une structure de promesse imbriquée, qui vous permet de lancer des appels HTTP imbriqués multiples et complètement asynchrones, et de joindre les résultats de chaque sous-ensemble d'appels via Promise.all .

NOTE Les API récentes de Spotify search nécessiteront un jeton d'accès à spécifier dans les en-têtes de demande:

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

donc, pour exécuter l'exemple suivant, vous devez mettre votre token d'accès dans les en-têtes de 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 .

53
répondu loretoparisi 2017-07-30 09:33:11

2017 réponse: vous pouvez maintenant faire exactement ce que vous voulez dans chaque navigateur et noeud actuels""

C'est assez simple:

  • retourner une promesse
  • utilisez le "attendez" , qui va dire à JavaScript d'attendre la promesse d'être résolu en une valeur (comme la réponse HTTP)
  • ajouter le 'async' mot-clé de la fonction parent

Voici une version de travail de votre code:

(async function(){

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

})()

sont pris en charge dans tous les navigateurs actuels et noeud 8

49
répondu mikemaccana 2018-10-01 10:06:17

vous pouvez utiliser cette bibliothèque personnalisée (écrite en utilisant la promesse) pour faire un appel à distance.

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'usage Simple:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
48
répondu Vinoth Rajendran 2016-12-17 10:59:13

une autre solution consiste à exécuter le code par l'intermédiaire de l'exécuteur séquentiel nsynjs .

si la fonction sous-jacente est promisifiée

nsynjs évaluera toutes les promesses séquentiellement, et mettra le résultat de la promesse dans data propriété:

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 promisifiée

Étape 1. Envelopper la fonction avec rappel dans nsynjs-aware wrapper (s'il a promisified version, vous pouvez sauter ce test):

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. Mettre la logique synchrone en fonction:

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

Étape 3. Exécuter la fonction de manière synchrone via nnsynjs:

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

Nsynjs évaluera pas à pas tous les opérateurs et expressions, en arrêtant l'exécution dans le cas où le résultat d'une certaine fonction lente n'est pas prêt.

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

43
répondu amaksr 2017-07-04 20:30:36

Js est un filetage simple.

Navigateur peut être divisé en trois parties:

1) Boucle D'Événement

2)API Web

3)File D'Attente D'Événements

Boucle d'Événement de pistes pour toujours je.e sorte de boucle infinie.Event Queue est l'endroit où toutes vos fonctions sont poussées sur un événement(exemple: click) ceci est un par un effectué hors de la file et mis dans la boucle D'événement qui exécutent ceci la fonction et le prépare soi-même pour ensuite l'un après premier est exécuté.Cela signifie que l'exécution d'une fonction ne commence pas avant que la fonction en file d'attente soit exécutée en boucle d'événements.

maintenant, pensons que nous avons poussé deux fonctions dans une file d'attente, l'une est pour obtenir des données à partir du serveur et l'autre utilise ces données.Nous avons d'abord appuyé sur la fonction serverRequest() dans la file d'attente, puis sur la fonction utiliseData (). la fonction serverRequest va dans la boucle d'événement et fait un appel au serveur comme nous jamais savoir combien de temps il faudra pour obtenir les données à partir du serveur donc, ce processus est censé prendre du temps et donc nous occupons notre boucle d'événement donc accrochant notre page, c'est là que L'API Web entre en jeu elle prend cette fonction de boucle d'événement et traite avec le serveur rendant la boucle d'événement libre afin que nous puissions exécuter la prochaine fonction à partir de la file d'attente.La fonction suivante dans la file d'attente est utiliseData () qui va en boucle mais en raison de l'absence de données disponibles il va gaspillage et l'exécution de la fonction suivante continue jusqu'à la fin de la file d'attente.(C'est appelé Async appelant I. e nous pouvons faire autre chose jusqu'à ce que nous obtenions des données)

supposons que notre fonction serverRequest() ait une instruction return dans un code, lorsque nous récupérons les données de l'API Web du serveur, elle les pousse 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 rendre quelque chose de L'appel asynchrone.

ainsi La Solution est callback ou promise .

une Image de l'une des réponses ici, explique correctement l'utilisation de callback... Nous donnons notre fonction (Fonction utilisant les données retournées du serveur) à la fonction serveur appelant.

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 on l'appelle

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

lire ici pour les nouvelles méthodes dans ECMA (2016/17) pour faire un appel async(@Felix Kling réponse en haut) https://stackoverflow.com/a/14220323/7579856

42
répondu Aniket Jha 2018-03-16 16:48:55

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

  1. Q - une bibliothèque de promesses pour JavaScript
  2. A+ Promises.js
  3. jQuery différé
  4. XMLHttpRequest API
  5. à l'Aide de rappel du concept, en tant Que mise en œuvre dans la première réponse

exemple: jQuery a reporté la mise en œuvre pour travailler avec des demandes multiples

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();
29
répondu Mohan Dere 2017-06-22 09:31:40

C'est une question très courante à laquelle nous faisons face tout en luttant contre les "mystères" du 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

C'est un simple appel de fonction synchrone (où chaque ligne de code est 'terminé avec son travail' avant la prochaine en séquence), et le résultat est le même que prévu.

maintenant, ajoutons un peu de twist, par introduire peu de retard dans notre fonction, de sorte que toutes les lignes de code ne sont pas 'terminées' dans l'ordre. Ainsi, il émule le comportement asynchrone de la fonction:

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

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

Donc voilà, ce retard vient de casser la fonctionnalité que nous attendions! Mais qu'est-ce qui s'est passé ? En fait, c'est assez logique si vous regardez le code. la fonction foo() , lors de l'exécution, ne renvoie rien( donc la valeur retournée est undefined ), mais elle démarre une minuterie, qui exécute une fonction après 1s retourner "wohoo'. Mais comme vous pouvez le voir, la valeur attribuée à bar est immédiatement retourné trucs de foo(), pas autre chose qui vient plus tard.

alors, comment aborder cette question?

demandons notre fonction pour un promesse . La promesse est vraiment sur ce que cela signifie : il signifie que la fonction vous garantit de fournir avec toute sortie il obtient dans l'avenir. si voyons 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'
});

donc, le résumé est-pour s'attaquer aux fonctions asynchrones comme les appels basés ajax etc, vous pouvez utiliser une promesse pour resolve la valeur (qui vous l'intention de revenir). Ainsi, en bref, vous résoudre valeur au lieu de retour , dans des fonctions asynchrones.

UPDATE (Promises with async/wait)

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

alors/catch version:

function fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

async / attente version:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }
29
répondu Anish K. 2018-10-09 17:33:32

utilisez une fonction callback() dans le succès foo() . Essayez de cette façon. Il 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();
27
répondu Mahfuzur Rahman 2017-11-17 09:15:45

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 vous faites ceci:

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

si vous avez besoin de cibler des navigateurs qui ne prennent pas en charge ES6, vous pouvez exécuter le code via Babel ou closure-compiler pour générer ECMAScript 5.

Le rappel ...args sont enveloppés dans un tableau et déstructuré, lorsque vous les lisez, de sorte que le motif peut faites face aux callbacks qui ont plusieurs arguments. Par exemple avec noeud fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
27
répondu James 2018-07-31 10:35:51

Short answer : votre méthode foo() retourne immédiatement, tandis que l'appel $ajax() exécute asynchrone après que la fonction retourne . Le problème est alors de savoir comment et où stocker les résultats récupérés par l'appel asynchrone une fois qu'il retourne.

plusieurs solutions ont été données dans ce fil. Peut-être la manière la plus simple est de passer un objet à la méthode foo() , et de stocker les résultats dans un membre de cet objet après l'appel asynchrone est terminée.

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() retournera toujours rien d'utile. Cependant, le résultat de l'appel asynchrone seront désormais stockées dans result.response .

25
répondu David R Tribble 2015-09-23 22:52:03

bien sûr, il y a beaucoup d'approches comme demande synchrone, promesse, 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 code snippet peut être réécrit un peu différent:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
17
répondu Khoa Bui 2018-03-07 14:23:16

la question était:

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

qui peut être interprété comme:"

Comment faire asynchrone code synchrone ?

la solution sera d'éviter les callbacks, et d'utiliser une combinaison de promesses et async / attente .

je voudrais donner un exemple d'une requête 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 . Il sera assez clair.)

permet d'abord d'activer l'utilisation de JQuery, pour avoir $ disponible en tant que S :

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

définir une fonction qui renvoie un 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 si c'était synchrone :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
17
répondu Pieter Jan Bonestroo 2018-05-24 08:36:33

nous Nous trouvons dans un univers qui semble progresser le long d'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 nous 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évues pour se produire dans le futur. Alors d'autres choses doivent se produire après ceux d'abord les choses se produire. C'est l'idée de base appelé "l'asynchronicité". Dans notre monde de plus en plus réseauté, le cas le plus courant d'asynchronisme est l'attente d'un système à distance pour répondre à une demande.

prenons un exemple. Appelle le laitier et commande du lait. Quand il s'agit, vous voulez mettre dans votre café. Vous ne pouvez pas mettre le lait dans votre café maintenant, parce qu'il n'est pas encore là. 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 a besoin de attendre pour order_milk pour finir avant qu'il exécute put_in_coffee . En d'autres termes, il ne sait pas que order_milk est asynchrone --est quelque chose qui ne va pas se traduire dans le lait jusqu'à un certain temps futur. JS, et d'autres langues déclaratives, exécutent une instruction après l'autre sans attendre.

l'approche classique de JS à ce problème, profitant du fait que JS supporte des fonctions en tant qu'objets de première classe qui peuvent être passés, est de passer une fonction en tant que paramètre à la requête asynchone, qu'elle invoquera alors quand elle aura terminé sa tâche dans le futur. C'est l'approche du" rappel". Il ressemble à ceci:

order_milk(put_in_coffee);

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

le problème avec cette approche de callback est qu'elle pollue la sémantique normale d'une fonction rapportant son résultat avec return ; au lieu de cela, les fonctions doivent signaler leurs résultats en appelant un callback donné comme paramètre. De plus, cette approche peut devenir rapidement lourde lorsqu'on doit faire face à des séquences d'événements plus longues. Par exemple, disons que je veux attendre le lait à mettre dans le café, et ensuite, et seulement ensuite effectuer une troisième étape, à savoir boire le café. Je finis par avoir besoin d'écrire quelque chose comme ceci:

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

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

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

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

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

Entrez promesses

C'était la motivation de la notion de "promesse", qui est un type particulier de valeur qui représente un avenir ou asynchrone résultat d'une certaine sorte. Cela peut représenter quelque chose qui s'est déjà produit, ou qui va se produire dans le futur, ou qui pourrait ne jamais se produire du tout. Les promesses ont une seule méthode, appelée then , à laquelle vous passez une action à exécuter lorsque le résultat que la promesse représente a été réaliser.

dans le cas de notre lait et café, nous concevons order_milk pour retourner une promesse pour le lait arrivant, puis spécifier put_in_coffee comme une then action, comme suit:

order_milk() . then(put_in_coffee)

un avantage de ceci est que nous pouvons les ficeler ensemble pour créer des séquences d'événements futurs ("chaînage"):

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

appliquons les promesses à votre problème particulier. Nous allons envelopper notre logique de demande à l'intérieur d'une fonction, qui renvoie une promesse:

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

en fait, tout ce que nous avons fait est d'ajouter un return à l'appel à $.ajax . Cela fonctionne parce que $.ajax de jQuery retourne déjà une sorte de promesse-comme chose. (Dans la pratique, sans entrer dans les détails, Nous préférerions conclure cet appel de manière à rendre une réelle promesse, ou utiliser une alternative à $.ajax qui le fait. Maintenant, si on veut charger le fichier et attendre qu'elle se termine et ensuite faire quelque chose, nous pouvons simplement dire

get_data() . then(do_something)

par exemple,

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

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

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

Le async mot-clé

mais il y a encore quelque chose de vaguement insatisfait de devoir écrire du code d'une façon si synchrone et tout à fait manière différente si asynchrone. Pour synchrone, nous écrivons

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 a besoin de attendre pour que le premier appel se termine avant qu'il exécute le second". Ne serait-ce pas bien s'il y avait si était un moyen de dire ça à JS? Il s'avère qu'il est le await mot-clé, utilisé à l'intérieur d'un type spécial de fonction appelée une fonction "async". Cette fonctionnalité fait partie de la prochaine version de 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 pouvez écrire quelque chose comme

async function foo() {
  data = await get_data();
  console.log(data);
}
15
répondu 3 revs, 2 users 95%user663031 2017-11-09 20:30:58

en utilisant ES2017 vous devriez avoir ceci comme la déclaration de fonction

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

et l'exécuter comme ceci.

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

})
10
répondu Fernando Carvajal 2018-01-24 06:18:55

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

il y a beaucoup de réponses informatives avec de grands 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 globale.

  1. votre point d'entrée est exécuté à la suite d'un événement. Pour exemple, une étiquette de script avec le code est chargée dans navigateur. (En conséquence, c'est pourquoi vous devrez peut-être concerné par l' état de préparation de la page pour exécuter votre code s'il nécessite des éléments dom pour être construit en premier, etc.)
  2. votre code s'exécute jusqu'à la fin--cependant beaucoup d'appels asynchrones il fait--sans exécuter n'importe quel de vos callbacks, y compris XHR les demandes, définir les délais, dom gestionnaires d'événements, etc. Chacun de ces rappels en attente d'être exécutés va s'asseoir dans une file d'attente, en attendant leur tour pour être courir après d'autres événements qui ont tiré ont tous terminé l'exécution.
  3. chaque appel individuel vers une requête XHR, un délai d'attente ou un dom l'événement une fois invoquée sera alors exécuté jusqu'à la fin.

La bonne nouvelle est que si vous comprenez bien, vous n'aurez jamais à vous soucier des conditions de course. Vous devriez d'abord et avant tout chose de la façon dont vous voulez organiser votre code comme essentiellement la réponse à différents événements discrets, et comment vous je veux les assembler dans une séquence logique. Vous pouvez utiliser des promesses ou un niveau supérieur nouveau async/wait comme outils à cette fin, ou vous pouvez rouler votre propre.

mais vous ne devez pas utiliser d'outils tactiques pour résoudre un problème jusqu'à ce que vous êtes à l'aise avec le domaine de problème réel. Dessiner une carte de ces dépendances pour savoir ce qui doit s'exécuter lorsque. Tenter une approche ad-hoc à tous ces callbacks ne va tout simplement pas vous servir bien.

9
répondu Haim Zamir 2017-10-29 08:36:37

plutôt que de vous lancer du code, il y a 2 concepts clés pour comprendre comment JS gère les callbacks et l'asynchronicité. (est-ce encore un mot?)

La Boucle d'Événement et de Simultanéité Modèle

il y a trois choses que vous devez savoir; la file d'attente; la boucle d'événement et la pile

In en termes généraux et simplistes, la boucle d'événements est comme le chef de projet, elle est constamment à l'écoute de 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 reçoit un message d'exécuter quelque chose, il l'ajoute à la file d'attente. La file d'attente est la liste des choses qui attendent d'être exécutées (comme votre requête AJAX). imaginez-le ainsi:

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

quand l'un de ces messages va l'exécuter pop le message de la file d'attente et crée une pile, la pile est tout le JS doit exécuter pour exécuter l'instruction dans le message. Dans notre exemple, on nous dit d'appeler foobarFunc

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

donc tout ce que foobarFunc doit exécuter (dans notre cas anotherFunction ) sera poussé sur la pile. exécuté , puis oublié - la boucle d'événement se déplacera alors sur la prochaine chose dans la file d'attente (ou écouter pour les messages)

l'essentiel ici est l'ordre d'exécution. C'est-à-dire

QUAND quelque chose va courir

lorsque vous faites un appel en utilisant AJAX à une partie externe ou exécutez n'importe quel code asynchrone (un setTimeout par exemple), Javascript dépend d'une réponse avant qu'elle ne puisse continuer.

la grande question est quand obtiendra-t-elle la réponse? La réponse est que nous ne savons pas-donc l'événement loop attend que ce message dise "Hey run me". Si JS attendait ce message en même temps, votre application gèlerait et ça serait nul. Ainsi JS continue à exécuter l'article suivant dans la file d'attente en attendant que le message soit ajouté de nouveau à 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 au I promise to return something at some point jQuery utilise des callbacks spécifiques appelés deffered.done deffered.fail et deffered.always (entre autres). Vous pouvez les voir tous ici

donc ce que vous devez faire est de passer une fonction qui est promise à exécuter à un moment donné avec des données qui lui sont passées.

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

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

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

J'espère que ça aura un sens. Quand vous rencontrez des choses comme ça qui semblent déroutantes - je recommande fortement de lire la documentation complètement pour au moins obtenir une compréhension de celui-ci. Cela fera de vous un bien meilleur développeur.

7
répondu Matthew Brent 2018-05-04 15:56:07