Question Comment renvoyer 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 la success rappel ainsi que l'attribution de la réponse à une variable locale à l'intérieur de la fonction et en renvoyant celle-ci, mais aucune de ces façons ne retourne 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`.

4307
2018-01-08 17:06


origine


Réponses:


-> 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 l'ai modifiée dans une fonction? - Référence de code asynchrone 

-> Si vous avez déjà compris le problème, passez aux solutions possibles ci-dessous.

Le problème

le UNE dans Ajax signifie asynchrone . Cela signifie que l'envoi de la requête (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 la déclaration suivante, return result;, est exécuté avant la fonction que vous avez transmise success rappel a même été appelé.

Voici une analogie qui, espérons-le, fait la différence entre le débit synchrone et le débit asynchrone:

Synchrone

Imaginez que vous appelez un ami et lui demandiez de chercher quelque chose pour vous. Même si cela peut prendre un certain temps, vous devez attendre au téléphone et regarder 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 effectuez 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 findItem peut prendre beaucoup de temps à exécuter, tout code venant après var item = findItem(); doit attendez jusqu'à ce que la fonction renvoie le résultat.

Asynchrone

Vous appelez 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 portable. Vous raccrochez, quittez la maison et faites ce que vous avez prévu de faire. Une fois que votre ami vous rappelle, vous avez affaire à l'information qu'il vous a donnée.

C'est exactement ce qui se passe quand vous faites une requête 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'appel Ajax est exécutée. Pour obtenir la réponse à terme, vous fournissez une fonction à appeler une fois la réponse reçue, rappeler (remarquez quelque chose? rappeler ?). Toute instruction postérieure à cet appel est exécutée avant l'appel du rappel.


Solutions)

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

Pourquoi est-ce mauvais, demandez-vous?

JavaScript s'exécute dans le thread d'interface utilisateur du navigateur et tout processus en cours d'exécution verrouille l'interface utilisateur, le rendant inactif. De plus, il y a une limite supérieure sur le temps d'exécution pour JavaScript et le navigateur demandera à l'utilisateur s'il doit continuer l'exécution ou non.

Tout cela est vraiment une mauvaise expérience utilisateur. L'utilisateur ne sera pas en mesure de dire si tout fonctionne correctement ou non. De plus, l'effet sera pire pour les utilisateurs avec une connexion lente.

Dans la suite, nous verrons trois solutions différentes qui se construisent les unes au-dessus des autres:

  • Des promesses avec async/await (ES2017 +, disponible dans les anciens navigateurs si vous utilisez un transpilateur ou un régénérateur)
  • Rappels (populaire dans le noeud)
  • Des promesses avec then() (ES2015 +, disponible dans les anciens navigateurs si vous utilisez l'une des nombreuses bibliothèques de promesses)

Tous les trois sont disponibles dans les navigateurs actuels et le nœud 7+. 


ES2017 +: Promesses avec async/await

La nouvelle version d'ECMAScript publiée en 2017 a été introduite support au niveau de la syntaxe pour les fonctions asynchrones. Avec l'aide de async et await, vous pouvez écrire de façon asynchrone dans un "style synchrone". Ne vous méprenez pas: le code est toujours asynchrone, mais il est plus facile à lire / comprendre.

async/await construit sur le dessus des promesses: un async La fonction renvoie toujours une promesse. await "déballe" une promesse et entraîne soit la valeur avec laquelle la promesse a été résolue, soit une erreur si la promesse a été rejetée.

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

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

Voici un exemple qui se base sur le 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 a second (just for the sake of this example)
    await delay(1000);
    // 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);
  });

Plus récent navigateur et nœud support des versions async/await. Vous pouvez également prendre en charge les anciens environnements en transformant votre code en ES5 à l'aide de régénérateur (ou des outils utilisant un régénérateur, tels que Babel).


Laisser les fonctions accepter rappels

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 foo accepter un rappel et l'utiliser comme success rappeler. 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 se référera à la fonction que nous passons à foo quand nous l'appelons et nous le passons simplement à success. C'est à dire. une fois la requête Ajax réussie, $.ajax appellera callback et passez la réponse au rappel (auquel on peut se référer avec result, puisque c'est ainsi que nous avons défini le rappel).

Vous pouvez également traiter la réponse avant de la passer 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 en utilisant des rappels que cela peut sembler. Après tout, JavaScript dans le navigateur est fortement axé sur les événements (événements DOM). Recevoir la réponse Ajax n'est rien d'autre qu'un événement.
Des difficultés peuvent survenir lorsque vous devez travailler avec du code tiers, mais la plupart des problèmes peuvent être résolus en ne tenant compte que du flux d'applications.


ES2015 +: Promesses avec puis()

le Promise API est une nouvelle fonctionnalité de ECMAScript 6 (ES2015), mais il a une bonne support du navigateur déjà. Il existe également de nombreuses bibliothèques qui implémentent l'API Promises standard et fournissent des méthodes supplémentaires pour faciliter l'utilisation et la composition des fonctions asynchrones (par ex. oiseau bleu).

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

L'avantage par rapport aux rappels simples 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 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écrire tous les avantages que l'offre de promesse est au-delà de la portée de cette réponse, mais si vous écrivez un nouveau code, vous devriez les considérer sérieusement. Ils fournissent une grande abstraction et une séparation de votre code.

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

Note secondaire: les objets différés de jQuery

Objets différés sont l'implémentation personnalisée des promesses de jQuery (avant que l'API Promise ne soit standardisée). 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'un objet différé) que vous pouvez simplement renvoyer de votre fonction:

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

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

Note de côté: Promise gotchas

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

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'asynchronisme ci-dessus. Plus précisément, $.ajax() ne gèle 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, renvoie immédiatement un objet jQuery Ajax Deferred, pas la réponse du serveur. Cela signifie que if déclaration va toujours obtenir cet objet différé, le traiter comme true, et procédez comme si l'utilisateur était connecté. Pas bon.

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 synchrones "Ajax"

Comme je l'ai mentionné, certaines opérations (!) Asynchrones ont des homologues synchrones. Je ne préconise pas leur utilisation, mais par souci d'exhaustivité, voici comment vous pourriez effectuer un appel synchrone:

Sans jQuery

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

jQuery

Si tu utilises jQuery, vous pouvez définir le async option à false. Notez que cette option est déconseillé depuis jQuery 1.8. Vous pouvez ensuite soit utiliser un success rappel ou accéder à la responseText propriété du 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 pour $.ajax(puisque vous ne pouvez transmettre que les paramètres de configuration à $.ajax).

La tête haute! Il n'est pas possible de faire un synchrone JSONP demande. JSONP par nature est toujours asynchrone (une raison de plus pour ne même pas considérer cette option).


4654
2018-01-08 17:06



Si vous êtes ne pas en utilisant jQuery dans votre code, cette réponse est pour vous

Votre code devrait être quelque chose comme 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'

Félix Kling a fait un excellent travail en écrivant une réponse pour les personnes qui utilisent jQuery pour AJAX, j'ai décidé de fournir une alternative pour les personnes qui ne le font pas.

(Notez, pour ceux qui utilisent le nouveau fetch API, Angulaire ou promesses J'ai ajouté une autre réponse ci-dessous)


Ce à 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 cela.

le UNE à AJAX signifie asynchrone. Cela signifie que l'envoi de la requête (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple, .send retourne immédiatement et la déclaration suivante, return result;, est exécuté avant la fonction que vous avez transmise success rappel a même été appelé.

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

Voici une analogie simple

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

(Violon)

La valeur de a retourné est undefined depuis le a=5 la partie 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 re-activement , en disant à 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);
}

C'est appelé CPS. Fondamentalement, nous passons getFive une action à effectuer quand elle se termine, nous disons à notre code comment réagir quand un événement se termine (comme notre appel AJAX, ou dans ce cas, le timeout).

L'utilisation serait:

getFive(onComplete);

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

Solutions possibles

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

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

1. Synaxe AJAX - Ne le fais pas !!

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

XMLHttpRequest prend en charge les communications synchrones et asynchrones. En général, cependant, les demandes asynchrones doivent être préférées aux requêtes synchrones pour des raisons de performances.

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

Si vous avoir 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 complète.

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, ce qui donne:

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, vérifiez 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 quand l'AJAX se termine avec succès, nous pouvons étendre ceci en vérifiant si le statut de réponse n'est pas 200 et en agissant en conséquence (créer un gestionnaire d'échec et tel). Résoudre efficacement notre problème.

Si vous avez encore du mal à comprendre cela lire le guide de démarrage AJAX à MDN.


886
2018-05-29 23:30



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 court XMLHttpRequest 2 qui fonctionne sur les navigateurs modernes et sur les navigateurs mobiles, je vous suggère de l'utiliser de la façon suivante:

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 supplémentaires).
  3. Il utilise le nouveau onload (vous n'avez donc pas besoin de vérifier le statut de readystate &&)
  4. Il y a d'autres situations dont je ne me souviens pas qui rendent le XMLHttpRequest 1 agaçant.

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

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 (ce qui précède 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 diront probablement qu'il vaut mieux utiliser onreadystatechange ou même le nom de la variable XMLHttpRequest. C'est faux.

Check-out XMLHttpRequest fonctionnalités avancées

Il a pris en charge 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 voulez obtenir les en-têtes sur l'état 2.

En utilisant le XMLHttpRequest Le nom de variable est une autre grosse erreur car vous devez exécuter le callback dans les 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 ça devient & 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 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 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 l'erreur && synchrone fait complètement casser le point de la réponse. Quelle est une bonne façon d'utiliser Ajax correctement?

Gestionnaire d'erreurs

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 de manière statique afin de ne pas compromettre la fonction. Le gestionnaire d'erreurs peut également être utilisé pour d'autres fonctions.

Mais pour vraiment sortir une erreur le seulement manière d'écrire une URL incorrecte, auquel cas chaque navigateur renvoie 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 du tableau blob ou quoi que ce soit ....

Même si vous passez 'POSTAPAPAP' comme méthode, il ne va pas générer d'erreur.

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

Dans le premier cas, l'erreur est à l'intérieur du displayAjax() en dessous de this.statusText comme Method not Allowed.

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

cross-domain non autorisé déclenche 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 mis à l'erreur.

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

Donc: Pas besoin de vérifier les 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 a obtenu une réponse ... ??? J'ai donc vérifié le dossier où se trouve le code HTML, et il y avait un fichier appelé 'x.xml'. Donc, même si vous oubliez l'extension de votre fichier XMLHttpRequest 2 TROUVERA IT. Je LOL'd


Lire un fichier synchrone

Ne fais pas ça.

Si vous voulez bloquer le navigateur pendant un certain temps, chargez un bon gros fichier txt synchrone.

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

Maintenant vous pouvez faire

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

Il n'y a pas d'autre moyen de le faire 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 vous possédez des fichiers de liste ou quoi que vous utilisiez toujours des fonctions différentes pour chaque demande ...

Seulement si vous avez une page où vous chargez toujours le même XML / JSON ou ce que vous avez 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 basique.

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 seulement GET ...

Mais vous pouvez faire beaucoup de choses avec XMLHttpRequest 2:

J'ai fait un gestionnaire de téléchargement (en utilisant les deux côtés avec resume, filereader, filesystem), divers convertisseurs de resizer d'image en utilisant canvas, peupler des bases de données websql avec base64images et beaucoup plus ... Mais dans ces cas vous devriez créer une fonction uniquement ... parfois vous avez besoin d'un blob, des buffers de tableau, vous pouvez définir des en-têtes, redéfinir le type MIME et il y en a beaucoup plus ...

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


302
2017-08-19 08:06



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, sauvegarde de BackboneJS ou toute bibliothèque de nœuds qui renvoie des promesses.

Votre code devrait être quelque chose comme 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 fait un excellent travail en écrivant une réponse pour les personnes utilisant jQuery avec des rappels pour AJAX. J'ai une réponse pour XHR natif. Cette réponse est pour l'utilisation générique des promesses soit sur le frontend ou le backend.


Le problème principal

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 renvoie une promesse, le then les gestionnaires sont toujours exécuté de manière asynchrone - c'est-à-dire après le code ci-dessous qui n'est pas dans un .then gestionnaire.

Cela signifie que lorsque vous revenez data la then gestionnaire que vous avez défini n'a pas encore été exécuté. Cela signifie que la valeur que vous renvoyez n'a pas été définie sur la valeur correcte 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 depuis le data = 5 la partie n'a pas encore été exécutée. Il va probablement s'exécuter en une seconde mais à ce moment-là, il n'est plus pertinent pour la valeur retournée.

Puisque l'opération n'est pas encore arrivée (AJAX, appel de serveur, IO, minuterie), vous renvoyez la valeur avant que la requête ait la chance de dire à votre code quelle est cette valeur.

Une solution possible à ce problème consiste à coder re-activement , en disant à votre programme ce qu'il faut faire lorsque le calcul est terminé. Les promesses le permettent activement en étant temporelles (sensibles au temps) dans la nature.

Rappel rapide sur les promesses

Une promesse est un valeur au fil du temps. Promises ont état, ils commencent comme en attente sans valeur et peuvent 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 que après quoi, il restera toujours au même état pour toujours. Vous pouvez attacher then gestionnaires de promesses d'extraire leur valeur et gérer les erreurs. then les gestionnaires permettent chaînage des appels. Les promesses sont créées par utilisant des API qui les renvoient. Par exemple, le remplacement AJAX plus moderne fetch ou jQuery $.get retournez les promesses.

Quand nous appelons .then sur une promesse et revenir quelque chose de cela - nous obtenons une promesse pour la valeur traitée. Si nous retournons une autre promesse, nous aurons des choses incroyables, mais tenons nos chevaux.

Avec des promesses

Voyons comment nous pouvons résoudre le problème ci-dessus avec des promesses. D'abord, montrons notre compréhension des états de promesses d'en haut en utilisant le Promise constructeur 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 retourner un valeur ce que nous ne pouvons pas faire à cause du modèle de concurrence - nous retournons un wrapper pour une valeur que nous pouvons déballer avec then. C'est comme une boîte que vous pouvez ouvrir avec then.

Appliquer ce

Cela reste le même 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 renvoyer des valeurs à partir d'appels déjà asynchrones, mais nous pouvons utiliser des promesses et les chaîner pour effectuer le traitement. Nous savons maintenant comment retourner 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ù ils étaient. 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 au cours de la séquence 1,2,3,3,3,3,.... qui peut être itéré. Bien que ce soit intéressant en soi et ouvre la porte à beaucoup de possibilités, il y a un cas intéressant particulier.

Si la séquence que nous produisons est une séquence d'actions plutôt que des 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 nombres, nous avons besoin d'une séquence de avenir valeurs - c'est-à-dire: promesses.

Cette astuce un peu compliquée 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, en écrivant un est quelques lignes courtes de code mais est au-delà de la portée de cette réponse. Je vais utiliser Bluebird Promise.coroutine 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 renvoie une promesse elle-même, que nous pouvons consommer à partir 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 encore standardisé, il y a plusieurs propositions en ce moment mais dans chacune d'elles vous pouvez await promettre. Ceci est juste "sucre" (syntaxe plus agréable) pour la proposition ES6 ci-dessus en ajoutant le async et await mots clés. Faire 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 renvoie toujours une promesse tout de même :)


243
2018-05-12 02:22



Vous utilisez incorrectement Ajax. L'idée n'est pas de renvoyer quoi que ce soit, mais plutôt de transmettre les données à quelque chose appelé une 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. Vous devez plutôt soit transmettre les données, soit faire ce que vous voulez avec directement dans la fonction de réussite.


192
2018-05-23 02:05



La solution la plus simple est de créer une fonction JavaScript et de l'appeler pour l'Ajax success rappeler.

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

184
2018-02-18 18:58



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

enter image description here


154
2017-08-11 14:17