Comment renvoyer la réponse d'un appel asynchrone ?
P粉668113768
P粉668113768 2023-08-23 12:49:32
0
2
568
<p>Comment renvoyer une réponse/un résultat d'une fonction <code>foo</code> qui effectue une requête asynchrone ? </p> <p>J'ai essayé de renvoyer la valeur du rappel et d'attribuer le résultat à une variable locale dans la fonction et de renvoyer cette variable, mais aucune de ces méthodes ne renvoie réellement de réponse - elles renvoient toutes <code>undefined< ou une variable La valeur initiale de <code>result</code>. </code></p><code> <p><strong>Exemple de fonction asynchrone qui accepte un rappel</strong> (en utilisant la fonction <code>ajax</code>) : </p> <pre class="brush:php;toolbar:false;">fonction foo() { résultat var ; $.ajax({ URL : '...', succès : fonction (réponse) { résultat = réponse ; // renvoie la réponse ; // <- J'ai également essayé celui-là } }); return result ; // Il renvoie toujours « non défini » }</pré> <p><strong>Exemple d'utilisation de Node.js :</strong></p> <pre class="brush:php;toolbar:false;">fonction foo() { résultat var ; fs.readFile("chemin/vers/fichier", fonction (erreur, données) { résultat = données ; // renvoie les données ; // <- J'ai aussi essayé celui-là }); return result ; // Il renvoie toujours « non défini » }</pré> <p><strong>Exemple de bloc <code>then</code> utilisant Promise : </strong></p> <pre class="brush:php;toolbar:false;">fonction foo() { résultat var ; fetch(url).then(fonction(réponse) { résultat = réponse ; // renvoie la réponse ; // <- J'ai également essayé celui-là }); return result ; // Il renvoie toujours « non défini » }</pré> <p><br /></p></code>
P粉668113768
P粉668113768

répondre à tous(2)
P粉334721359

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

Votre code devrait ressembler à ceci :

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

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

Felix Kling a fait un excellent travailen écrivant la réponse pour les personnes utilisant jQuery pour AJAX, mais j'ai décidé de proposer une alternative aux personnes qui n'utilisent pas jQuery.

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


Problèmes auxquels vous êtes confronté

Voici un bref résumé de "l'explication de la question" d'une autre réponse, si vous n'êtes pas sûr après avoir lu ceci, veuillez lire cette réponse.

Le

A dans AJAX signifie asynchrone. Cela signifie que l'envoi de la requête (ou plutôt la réception de la réponse) est supprimé du flux normal d'exécution. Dans votre exemple, .send code>.send code> 立即返回,并且在调用您作为 success 回调传递的函数之前执行下一条语句 return result; renvoie immédiatement et l'instruction suivante return result; est exécutée avant d'appeler la fonction que vous avez passée comme rappel success .code>.

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 avez renvoyée n'a pas encore été définie.

Voici une analogie simple :

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

(violon)

Grâce à a=5 部分尚未执行,因此返回的 a 值为 undefined. AJAX se comporte de telle manière que vous renvoyez la valeur avant que le serveur n'ait la possibilité d'indiquer à votre navigateur quelle est la valeur.

Une solution possible à ce problème consiste à réactivement écrire du code qui indique à votre programme quoi faire une fois le calcul 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 transmettons getFive une action à effectuer une fois terminée, et nous indiquons à notre code comment réagir lorsque l'événement se termine (comme notre appel AJAX, ou dans ce cas, un délai d'attente).

L'utilisation est :

getFive(onComplete);

"5" apparaîtra à l'écran. (violon).

Solutions possibles

Il existe essentiellement deux manières de résoudre ce problème :

  1. Rendre les appels AJAX synchrones (nous l'appelons SJAX).
  2. Refactorisez votre code pour qu'il fonctionne correctement avec les rappels.

1. AJAX synchrone – Ne le faites pas ! !

Quant à l'AJAX synchrone, Ne le faites pas ! La réponse de Félix présente des arguments convaincants expliquant pourquoi c'est une mauvaise idée. Dans l'ensemble, cela gèle le navigateur de l'utilisateur jusqu'à ce que le serveur renvoie une réponse et crée une très mauvaise expérience utilisateur. Voici un autre bref résumé de MDN expliquant pourquoi :

Si vous devez faire cela, vous pouvez passer un drapeau. La méthode spécifique est la suivante :

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. Réorganiser le code

Faites en sorte que votre fonction accepte les rappels. Dans l'exemple de code, vous pouvez savoir comment foo 接受回调。我们将告诉代码当 foo réagit lorsque est terminé.

Donc :

var result = foo();
// Code that depends on `result` goes here

devient :

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

Ici, nous transmettons une fonction anonyme, mais nous pourrions tout aussi bien transmettre une référence à une fonction existante, la faisant ressembler à :

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

Pour plus de détails sur la façon de réaliser ce type de conception de rappel, consultez la réponse de Félix.

Maintenant, définissons foo lui-même pour fonctionner 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 la fonction foo qui accepte une action à exécuter lorsque AJAX se termine avec succès. Nous pouvons étendre davantage cette fonctionnalité en vérifiant si l'état de la réponse n'est pas 200 et en prenant les mesures appropriées (création d'un gestionnaire d'échec, etc.). Cela a effectivement résolu notre problème.

Si vous avez toujours du mal à comprendre cela, Lisez AJAX pour un guide pour démarrer sur MDN.

P粉642920522

Question

Le

A dans Ajax signifie asynchrone. Cela signifie que l'envoi de la requête (ou plutôt la réception de la réponse) est supprimé du flux normal d'exécution. Dans votre exemple, $.ajax 立即返回,并且下一条语句 return result; 在您作为 success le rappel est passé avant même que la fonction ne soit appelée.

Voici une analogie qui, espérons-le, rendra plus claire la différence entre les flux synchrones et asynchrones :

Sync

Imaginez que vous appeliez un ami et lui demandiez de trouver des informations pour vous. Même si cela peut prendre un certain temps, vous attendez près du téléphone, le regard dans le vide, jusqu'à ce que votre ami vous donne la réponse dont vous avez 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();

Bien que tout code après findItem 可能需要很长时间才能执行,但 var item = findItem(); doive attendre jusqu'à ce que la fonction renvoie le résultat.

Asynchrone

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

C'est exactement ce qui se passe lorsque vous faites une requête Ajax.

findItem(function(item) {
    // Do something with the item
});
doSomethingElse();

N'attend pas de réponse, mais continue l'exécution immédiatement et exécute l'instruction après l'appel Ajax. Afin d'obtenir enfin la réponse, vous devez fournir une fonction qui est appelée après la réception de la réponse, un rappel (vous avez remarqué quelque chose ? Un rappel ?). Toutes les instructions après cet appel seront exécutées avant que le rappel ne soit appelé.


Solution

Adoptez la nature asynchrone de JavaScript ! Bien que certaines opérations asynchrones fournissent des contreparties synchrones (comme le fait "Ajax"), leur utilisation est généralement déconseillée, notamment dans un contexte de navigateur.

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 peut verrouiller l'interface utilisateur, la rendant insensible. De plus, il existe une limite supérieure au temps d'exécution de JavaScript et le navigateur demandera à l'utilisateur s'il doit poursuivre l'exécution.

Tout cela conduit à une très mauvaise expérience utilisateur. L'utilisateur ne pourra pas savoir si tout fonctionne correctement. De plus, l’effet sera pire pour les utilisateurs dont la vitesse Internet est plus lente.

Nous présentons ci-dessous trois solutions différentes qui s'appuient toutes les unes sur les autres :

  • Promesses avec async/await (ES2017+, fonctionne dans les anciens navigateurs si vous utilisez un transpileur ou un régénérateur)
  • Rappels (populaire dans le nœud)
  • Avec then() 的 Promise (ES2015+, fonctionne dans les anciens navigateurs si vous utilisez l'une des nombreuses bibliothèques Promise)

Ces trois fonctionnalités sont disponibles dans les navigateurs actuels et Node 7+.


ES2017+ : Utilisez async/await 进行承诺

La version 2017 d'ECMAScript a introduit la prise en charge au niveau de la syntaxe pour les fonctions asynchrones. Avec asyncawait vous pouvez écrire de manière asynchrone dans un "style synchrone". Le code est toujours asynchrone, mais plus facile à lire/comprendre.

async/await 构建在 Promise 之上:async 函数始终返回 Promise。 await Construit sur une promesse : les fonctions

async renvoient toujours une promesse.

await "déverrouille" une promesse et produit soit la valeur à laquelle la promesse est résolue, soit génère une erreur si la promesse est rejetée. async 函数或 JavaScript 模块。模块外部不支持顶级 await,因此您可能必须创建异步 IIFE (立即调用函数表达式)来启动异步IMPORTANT

 : Vous ne pouvez utiliser

await que dans les fonctions async ou Modules JavaScript. Le niveau supérieur await n'est pas pris en charge en dehors du module, vous devrez donc peut-être créer un IIFE asynchrone (Expression de fonction immédiatement invoquée ) pour démarrer le contexte async (si vous n'utilisez pas de modules). Vous pouvez en savoir plus sur

async

et findItem()await

. Voici un exemple détaillant la fonction delayed async/awaitfindItem() 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 superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();
Les versions actuelles de browser et

node
prennent en charge

. Vous pouvez également convertir votre code en ES5 à l'aide de regenerator

(ou d'outils utilisant le régénérateur) pour prendre en charge des environnements plus anciens tels que

Babel

). 🎜 🎜 🎜Laissez la fonction accepter les rappels🎜🎜 🎜Le rappel a lieu lorsque la fonction 1 est transmise à la fonction 2. La fonction 2 peut appeler la fonction 1 lorsqu'elle est prête. Dans le contexte d'un processus asynchrone, le rappel est appelé chaque fois que le processus asynchrone se termine. Normalement, les résultats sont transmis au rappel. 🎜

Dans l'exemple de la question, vous pouvez effectuer foo 接受回调并将其用作 success un rappel. Alors ça

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

est devenu

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

Ici, nous définissons une fonction "inline", mais vous pouvez passer n'importe quelle référence de fonction :

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

foo(myCallback);

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

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

callback fera référence à la fonction à laquelle nous avons passé callback 将引用我们调用时传递给 foo 的函数,并将其传递给 success。 IE。一旦Ajax请求成功,$.ajax将调用callback并将响应传递给回调(可以用result lorsque nous l'avons appelée, et la transmettra à success. c'est à dire. Une fois la requête Ajax réussie, $.ajax appellera callback et transmettra la réponse au rappel (qui peut être référencée avec result, puisque c'est ainsi que nous définissons le rappel (The way).

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

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

Écrire du code à l'aide de rappels est plus facile qu'il n'y paraît. Après tout, JavaScript dans les navigateurs est largement piloté par les événements (événements DOM). Recevoir une réponse Ajax n'est rien de plus qu'un événement. Des difficultés peuvent survenir lorsque vous devez utiliser du code tiers, mais la plupart des problèmes peuvent être résolus en pensant simplement au flux de l'application.


ES2015+ : avec then()的 Promise >

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

La promesse est un conteneur de valeurs futures. Lorsqu'une promesse reçoit une valeur (résolue) ou est annulée (rejetée), elle avertit tous les « auditeurs » qui souhaitent accéder à la valeur.

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

Voici un exemple d'utilisation de 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);
  });
}

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).
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal