→ Pour une explication plus générale du comportement asynchrone avec différents exemples, veuillez consulter Pourquoi ma variable n'est-elle pas modifiée après l'avoir 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 A à Ajax signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple, $.ajax
renvoie 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
rappel ne soit appelée.
Voici une analogie qui, nous l'espérons, fait la différence entre le flux synchrone et asynchrone plus clair:
Synchrone
Imaginez que vous appeliez un ami et lui demandiez de rechercher quelque chose pour vous. Bien que cela puisse prendre un certain temps, vous attendez au téléphone et 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 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 l' findItem
exécution peut prendre beaucoup de temps, tout code suivant var item = findItem();
doit attendre 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 pressé et qu'il devrait vous rappeler sur votre téléphone portable. Vous raccrochez, quittez la maison et faites tout ce que vous avez prévu de faire. Une fois que votre ami vous rappelle, vous traitez avec les informations qu'il vous a données.
C'est exactement ce qui se passe lorsque vous faites une demande Ajax.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
Au lieu d'attendre la réponse, l'exécution se poursuit immédiatement et l'instruction après l'exécution de l'appel Ajax. Pour obtenir la réponse à terme, vous fournissez une fonction à appeler une fois la réponse reçue, un rappel (remarquez quelque chose? Rappelez-vous ?). Toute instruction venant après cet appel est exécutée avant l'appel du rappel.
Solutions)
Adoptez la nature asynchrone de JavaScript! Bien que certaines opérations asynchrones fournissent des homologues synchrones (tout comme "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 de longue durée verrouillera l'interface utilisateur, ce qui la rendra insensible. De plus, il y a une limite supérieure sur le temps d'exécution pour JavaScript et le navigateur demandera à l'utilisateur de poursuivre ou non l'exécution.
Tout cela est une très mauvaise expérience utilisateur. L'utilisateur ne pourra pas dire si tout fonctionne bien ou non. De plus, l'effet sera pire pour les utilisateurs avec une connexion lente.
Dans ce qui suit, nous examinerons trois solutions différentes qui se construisent toutes les unes sur les autres:
- Promesses avec
async/await
(ES2017 +, disponible dans les anciens navigateurs si vous utilisez un transpilateur ou un régénérateur)
- Rappels (populaires dans le nœud)
- Promesses avec
then()
(ES2015 +, disponible dans les anciens navigateurs si vous utilisez l'une des nombreuses bibliothèques de promesses)
Les trois sont disponibles dans les navigateurs actuels et le nœud 7+.
ES2017 +: promesses avec async/await
La version ECMAScript publiée en 2017 a introduit la prise en charge au niveau syntaxique des fonctions asynchrones. Avec l'aide de async
et await
, vous pouvez écrire asynchrone dans un "style synchrone". Le code est toujours asynchrone, mais il est plus facile à lire / à comprendre.
async/await
s'appuie sur des promesses: une async
fonction renvoie toujours une promesse. await
"déballe" une promesse et aboutit à la valeur avec laquelle la promesse a été résolue ou génère une erreur si la promesse a été rejetée.
Important: Vous ne pouvez utiliser qu'à l' await
intérieur d'une async
fonction. Pour le moment, le niveau supérieur await
n'est pas encore pris en charge, vous devrez donc peut-être créer une async IIFE ( Immediateely Invoked Function Expression ) pour démarrer un async
contexte.
Vous pouvez en savoir plus sur async
et await
sur MDN.
Voici un exemple qui s'appuie sur le délai ci-dessus:
// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
Prise en charge des versions actuelles du navigateur et des nœudsasync/await
. Vous pouvez également prendre en charge des environnements plus anciens en transformant votre code en ES5 à l'aide de régénérateur (ou d'outils utilisant régénérateur, tels que Babel ).
Laisser les fonctions accepter les rappels
Un rappel est simplement une fonction passée à 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 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
rappel. 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 "en ligne" 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 à foo
laquelle nous passons lorsque nous l'appelons et nous la transmettons simplement à success
. C'est-à-dire une fois que la demande Ajax est réussie,$.ajax
appellera callback
et passera la réponse au rappel (auquel on peut se référer result
, puisque c'est ainsi que nous avons défini le rappel).
Vous pouvez également traiter la réponse avant de la transmettre au rappel:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
Il est plus facile d'écrire du code à l'aide de rappels qu'il n'y paraît. Après tout, JavaScript dans le navigateur est fortement piloté par les événements (événements DOM). La réception de 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 réfléchissant simplement au flux d'application.
ES2015 +: promesses avec then ()
L' API Promise est une nouvelle fonctionnalité d'ECMAScript 6 (ES2015), mais elle prend déjà en charge un bon navigateur . 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 exemple bluebird ).
Les promesses sont des conteneurs pour les valeurs futures . Lorsque la promesse reçoit la valeur (elle est résolue ) ou lorsqu'elle est annulée ( rejetée ), elle avertit tous ses "écouteurs" qui souhaitent 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 celle-ci:
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Décrire tous les avantages de cette promesse dépasse le cadre de cette réponse, mais si vous écrivez du nouveau code, vous devez sérieusement les considérer. Ils fournissent une excellente abstraction et séparation de votre code.
Plus d'informations sur les promesses: HTML5 rocks - JavaScript Promises
Note latérale: les objets différés de jQuery
Les objets différés sont l'implémentation personnalisée de promesses par jQuery (avant la normalisation de l'API Promise). Ils se comportent presque comme des promesses mais exposent une API légèrement différente.
Chaque méthode Ajax de jQuery retourne 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é: promesses gotchas
Gardez à l'esprit que les promesses et les objets différés ne sont que des conteneurs pour une valeur future, ils ne sont pas la valeur elle-même. Par exemple, supposons que vous disposiez des éléments suivants:
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
Ce code comprend mal les problèmes d'asynchronie ci-dessus. Plus précisément, $.ajax()
ne gèle pas le code pendant qu'il vérifie la page `` / mot de passe '' sur votre serveur - il envoie une demande au serveur et pendant qu'il attend, il renvoie immédiatement un objet jQuery Ajax Deferred, pas la réponse du serveur. Cela signifie que l' if
instruction va toujours obtenir cet objet différé, le traiter comme true
et procéder comme si l'utilisateur était connecté. Pas bon.
Mais la solution est simple:
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
});
Déconseillé: appels "Ajax" synchrones
Comme je l'ai mentionné, certaines (!) Opérations asynchrones ont des homologues synchrones. Je ne préconise pas leur utilisation, mais pour des raisons d'exhaustivité, voici comment effectuer un appel synchrone:
Sans jQuery
Si vous utilisez directement un XMLHTTPRequest
objet, passez false
comme troisième argument à .open
.
jQuery
Si vous utilisez jQuery , vous pouvez définir l' async
option sur false
. Notez que cette option est déconseillée depuis jQuery 1.8. Vous pouvez alors soit toujours utiliser un success
rappel, soit accéder à la responseText
propriété de l' objet jqXHR :
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
Si vous utilisez une autre méthode jQuery Ajax, tels que $.get
, $.getJSON
, etc., vous devez changer à $.ajax
(puisque vous ne pouvez passer des paramètres de configuration$.ajax
).
La tête haute! Il n'est pas possible de faire une demande JSONP synchrone . JSONP par sa nature même est toujours asynchrone (une raison de plus pour ne même pas considérer cette option).
If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.
(Oui, je réalise que mon pseudo est un peu ironique dans ce cas)foo
est appelé et qu'une fonction lui est passée (foo(function(result) {....});
)?result
est utilisé à l'intérieur de cette fonction et est la réponse de la requête Ajax. Pour faire référence à cette fonction, le premier paramètre de foo est appelécallback
et assigné à lasuccess
place d'une fonction anonyme. Donc,$.ajax
j'appelleracallback
quand la demande a réussi. J'ai essayé de l'expliquer un peu plus.$.getJSON
si vous souhaitez que la demande Ajax soit synchrone. Cependant, vous ne devez pas souhaiter que la demande soit synchrone, donc cela ne s'applique pas. Vous devez utiliser des rappels ou des promesses pour gérer la réponse, comme cela est expliqué plus haut dans la réponse.Si vous n'utilisez pas jQuery dans votre code, cette réponse est pour vous
Votre code devrait être quelque chose dans le sens de ceci:
Felix Kling a fait un excellent travail en écrivant une réponse pour les personnes utilisant jQuery pour AJAX, j'ai décidé de fournir une alternative pour les personnes qui ne le sont pas.
( Remarque, pour ceux qui utilisent la nouvelle
fetch
API, Angular ou promesses, j'ai ajouté une autre réponse ci-dessous )Ce à quoi vous faites face
Ceci est un bref 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 A dans AJAX signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple,
.send
renvoie 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 quesuccess
rappel ne soit appelée.Cela signifie que lorsque vous revenez, l'écouteur que vous avez défini ne s'est pas encore exécuté, ce qui signifie que la valeur que vous retournez n'a pas été définie.
Voici une analogie simple
(Violon)
La valeur de
a
retournée estundefined
puisque laa=5
partie n'a pas encore été exécutée. AJAX agit comme ceci, vous retournez la valeur avant que le serveur n'ait la possibilité de dire à votre navigateur quelle est cette valeur.Une solution possible à ce problème consiste à coder de manière réactive , en indiquant à votre programme quoi faire une fois le calcul terminé.
C'est ce qu'on appelle CPS . En gros, on passe
getFive
une action à effectuer lorsqu'elle se termine, nous disons à notre code comment réagir à la fin d'un événement (comme notre appel AJAX, ou dans ce cas le délai d'expiration).L'utilisation serait:
Ce qui devrait alerter "5" à l'écran. (Violon) .
Solutions possibles
Il existe essentiellement deux façons de résoudre ce problème:
1. AJAX synchrone - Ne le faites pas !!
Quant à l'AJAX synchrone, ne le faites pas! La réponse de Felix soulève des arguments convaincants pour expliquer pourquoi c'est une mauvaise idée. Pour résumer, cela gèle 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 court résumé tiré de MDN sur pourquoi:
Si vous devez le faire, vous pouvez passer un drapeau: Voici comment:
2. Code de restructuration
Laissez votre fonction accepter un rappel. Dans l'exemple, le code
foo
peut être fait pour accepter un rappel. Nous dirons à notre code comment réagir une foisfoo
terminé.Donc:
Devient:
Ici, nous avons transmis une fonction anonyme, mais nous pourrions tout aussi facilement transmettre une référence à une fonction existante, la faisant ressembler à:
Pour plus de détails sur la façon dont ce type de conception de rappel est effectué, consultez la réponse de Felix.
Maintenant, définissons foo lui-même pour agir en conséquence
(violon)
Nous avons maintenant fait en sorte que notre fonction foo accepte une action à exécuter lorsque l'AJAX se termine avec succès, nous pouvons l'étendre davantage en vérifiant si l'état de la réponse n'est pas 200 et en agissant en conséquence (créez un gestionnaire d'échec, etc.). Résoudre efficacement notre problème.
Si vous avez encore du mal à comprendre cela, lisez le guide de démarrage AJAX sur MDN.
la source
onload
gestionnaire, qui ne se déclenche que lorsqu'il l'readyState
est4
. Bien sûr, il n'est pas pris en charge dans IE8. (iirc, peut avoir besoin de confirmation.)XMLHttpRequest 2 (tout d'abord, lisez les réponses de Benjamin Gruenbaum & Felix Kling )
Si vous n'utilisez pas jQuery et que vous voulez un joli court XMLHttpRequest 2 qui fonctionne sur les navigateurs modernes et également sur les navigateurs mobiles, je suggère de l'utiliser de cette façon:
Comme vous pouvez le voir:
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:
Ou si pour une raison quelconque vous
bind()
rappelez une classe:Exemple:
Ou (celui ci-dessus est préférable, les fonctions anonymes sont toujours un problème):
Rien de plus simple.
Maintenant, certaines personnes diront probablement qu'il vaut mieux utiliser onreadystatechange ou même le nom de variable XMLHttpRequest. C'est faux.
Check-out les fonctionnalités avancées de XMLHttpRequest
Il supportait tous les * navigateurs modernes. Et je peux confirmer que j'utilise cette approche car XMLHttpRequest 2 existe. Je n'ai jamais eu aucun type de problème sur tous les navigateurs que j'utilise.
onreadystatechange n'est utile que si vous souhaitez obtenir les en-têtes sur l'état 2.
L'utilisation du
XMLHttpRequest
nom de variable est une autre grosse erreur car vous devez exécuter le rappel 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:
Encore une fois ... c'est une fonction très courte, mais elle obtient et publie.
Exemples d'utilisation:
Ou passez un élément de formulaire complet (
document.getElementsByTagName('form')[0]
):Ou définissez des valeurs personnalisées:
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 && synchronous brise complètement le point de la réponse. Quel est le bon moyen d'utiliser Ajax correctement?
Gestionnaire d'erreurs
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, la seule façon est d'écrire une mauvaise URL, auquel cas chaque navigateur renvoie une erreur.
Les gestionnaires d'erreurs peuvent être utiles si vous définissez des en-têtes personnalisés, définissez responseType sur un tampon de tableau d'objets blob ou autre ...
Même si vous passez 'POSTAPAPAP' comme méthode, cela ne générera pas d'erreur.
Même si vous passez 'fdggdgilfdghfldj' comme formdata, il ne générera pas d'erreur.
Dans le premier cas, l'erreur se trouve à l'intérieur du
displayAjax()
sousthis.statusText
commeMethod not Allowed
.Dans le deuxième cas, cela fonctionne simplement. Vous devez vérifier côté serveur si vous avez transmis les bonnes données de publication.
cross-domain non autorisé lève automatiquement l'erreur.
Dans la réponse d'erreur, il n'y a aucun code d'erreur.
Il n'y a que le
this.type
qui est réglé sur erreur.Pourquoi ajouter un gestionnaire d'erreurs si vous n'avez aucun contrôle sur les erreurs? La plupart des erreurs sont renvoyées à l'intérieur de cela dans la fonction de rappel
displayAjax()
.Donc: pas besoin de vérifier les erreurs si vous pouvez copier et coller l'URL correctement. ;)
PS: Lors du premier test, j'ai écrit x ('x', displayAjax) ..., et il a totalement 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 LE TROUVERA . J'ai lol
Lire un fichier synchrone
Ne fais pas ça.
Si vous souhaitez bloquer le navigateur pendant un certain temps, chargez un joli gros
.txt
fichier synchrone.Maintenant vous pouvez faire
Il n'y a pas d'autre moyen de le faire de manière non asynchrone. (Ouais, avec la boucle setTimeout ... mais sérieusement?)
Un autre point est ... si vous travaillez avec des API ou tout simplement les fichiers de votre propre 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 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 une utilisation de base.
Si vous souhaitez PROLONGER 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 uniquement ...
Mais vous pouvez faire beaucoup de choses avec XMLHttpRequest 2:
J'ai fait un gestionnaire de téléchargement (en utilisant des plages des deux côtés avec CV, filereader, système de fichiers), divers convertisseurs de resizers d'image utilisant canvas, peupler les bases de données web SQL avec base64images et bien plus encore ... Mais dans ces cas, vous ne devez créer une fonction que pour cela but ... parfois vous avez besoin d'un blob, de tampons de tableau, vous pouvez définir des en-têtes, remplacer le mimetype et il y en a beaucoup plus ...
Mais la question ici est de savoir comment renvoyer une réponse Ajax ... (J'ai ajouté un moyen simple.)
la source
x
,ajax
ouxhr
peut-être plus joli :)). Je ne vois pas comment cela résout le retour de la réponse d'un appel AJAX. (quelqu'un pourrait encore fairevar res = x("url")
et ne pas comprendre pourquoi cela ne fonctionne pas;)). Sur une note latérale - ce serait cool si vous reveniezc
de la méthode afin que les utilisateurs puissent se connectererror
etc.2.ajax is meant to be async.. so NO var res=x('url')..
C'est tout l'intérêt de cette question et des réponses :)Si vous utilisez des promesses, cette réponse est pour vous.
Cela signifie AngularJS, jQuery (avec différé), le remplacement natif de XHR (récupération), EmberJS, la sauvegarde de BackboneJS ou toute bibliothèque de nœuds qui renvoie des promesses.
Votre code devrait être quelque chose dans le sens de ceci:
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 concerne l'utilisation générique des promesses 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 renvoie une promesse, les
then
gestionnaires sont toujours exécutés de manière asynchrone - c'est-à-dire après le code en dessous d'eux qui n'est pas dans un.then
gestionnaire.Cela signifie que lorsque vous retournez
data
lethen
gestionnaire que vous avez défini ne s'est pas encore exécuté. Cela signifie à son tour que la valeur que vous retournez n'a pas été définie à la bonne valeur dans le temps.Voici une analogie simple pour le problème:
La valeur de
data
estundefined
depuis ledata = 5
partie n'a pas encore été exécutée. Il s'exécutera probablement dans une seconde, mais à ce moment-là, il n'est plus pertinent pour la valeur renvoyée.Étant donné que l'opération n'a pas encore eu lieu (AJAX, appel serveur, IO, minuterie), vous retournez la valeur avant que la demande n'ait la possibilité de dire à votre code quelle est cette valeur.
Une solution possible à ce problème consiste à coder de manière réactive , en indiquant à votre programme quoi faire une fois le calcul terminé. Les promesses permettent activement cela en étant de nature temporelle (sensible au temps).
Récapitulation rapide des promesses
Une promesse est une valeur dans le temps . Les promesses ont un état, elles commencent comme en attente sans valeur et peuvent se régler pour:
Une promesse ne peut changer d'état qu'une seule fois, après quoi elle restera toujours dans le même état pour toujours. Vous pouvez associer des
then
gestionnaires à des promesses pour extraire leur valeur et gérer les erreurs.then
les gestionnaires permettent d' enchaîner les appels. Les promesses sont créées à l' aide d'API qui les renvoient . Par exemple, le remplacement AJAX plus modernefetch
ou jQuery$.get
promesses de retour .Lorsque nous faisons appel
.then
à une promesse et que nous en retournons quelque chose, nous obtenons une promesse pour la valeur traitée . Si nous rendons 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. Tout d'abord, démontrons notre compréhension des états promis d'en haut en utilisant le constructeur Promise pour créer une fonction de retard:
Maintenant, après avoir converti setTimeout pour utiliser des promesses, nous pouvons utiliser
then
pour le faire compter:Fondamentalement, au lieu de renvoyer une valeur que nous ne pouvons pas faire à cause du modèle de concurrence - nous retournons un wrapper pour une valeur que nous pouvons dérouler avec
then
. C'est comme une boîte avec laquelle vous pouvez ouvrirthen
.Appliquer cela
Cela est le même pour votre appel d'API d'origine, vous pouvez:
Donc ça marche aussi bien. Nous avons appris que nous ne pouvons pas renvoyer des valeurs d'appels déjà asynchrones, mais nous pouvons utiliser des promesses et les enchaîner pour effectuer le traitement. Nous savons maintenant comment renvoyer la réponse d'un appel asynchrone.
ES2015 (ES6)
ES6 introduit des générateurs qui sont des fonctions qui peuvent revenir au milieu puis reprendre le point où elles étaient. Ceci est généralement utile pour les séquences, par exemple:
Est une fonction qui renvoie un itérateur sur la séquence
1,2,3,3,3,3,....
qui peut être itérée. Bien que cela soit intéressant en soi et ouvre de nombreuses possibilités, il existe un cas particulier intéressant.Si la séquence que nous produisons est une séquence d'actions plutôt que des nombres - nous pouvons mettre la fonction en pause chaque fois qu'une action est produite et l'attendre avant de reprendre la fonction. Donc, au lieu d'une séquence de nombres, nous avons besoin d'une séquence d' avenir valeurs - c'est-à-dire: des promesses.
Cette astuce quelque 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, en écrire une est quelques courtes lignes de code mais dépasse le cadre de cette réponse. J'utiliserai Bluebird
Promise.coroutine
ici, mais il y a d'autres emballages commeco
ouQ.async
.Cette méthode renvoie une promesse elle-même, que nous pouvons consommer à partir d'autres coroutines. Par exemple:
ES2016 (ES7)
Dans ES7, c'est encore plus standardisé, il y a plusieurs propositions en ce moment mais dans toutes, vous pouvez le
await
promettre. Ceci est juste du "sucre" (syntaxe plus agréable) pour la proposition ES6 ci-dessus en ajoutant les motsasync
-await
clés et . Faire l'exemple ci-dessus:Cela renvoie quand même une promesse :)
la source
return await data.json();
?)Vous utilisez Ajax de manière incorrecte. L'idée n'est pas de lui faire retourner 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:
Renvoyer quoi que ce soit dans le gestionnaire de soumission ne fera rien. Vous devez au lieu de cela remettre les données ou faire ce que vous voulez directement avec elles dans la fonction de réussite.
la source
success: handleData
et cela fonctionnerait.success
méthode, c'est la portée environnante de$.ajax
.La solution la plus simple consiste à créer une fonction JavaScript et à l'appeler pour le
success
rappel Ajax .la source
Je répondrai avec une bande dessinée à la main horrible. La deuxième image est la raison pour laquelle
result
estundefined
dans votre exemple de code.la source
Angulaire1
Pour les personnes qui utilisent AngularJS , peuvent gérer cette situation en utilisant
Promises
.Ici, il est dit,
Vous pouvez également trouver une belle explication ici .
Exemple trouvé dans les documents mentionnés ci-dessous.
Angular2 et versions ultérieures
En
Angular2
regardant l'exemple suivant, mais il est recommandé de l'utiliserObservables
avecAngular2
.}
Vous pouvez consommer cela de cette façon,
Voir le post original ici. Mais Typescript ne prend pas en charge les promesses natives d'es6 , si vous voulez l'utiliser, vous pourriez avoir besoin d'un plugin pour cela.
De plus, voici les promesses spécifiées ici.
la source
La plupart des réponses ici donnent des suggestions utiles lorsque vous avez une seule opération asynchrone, mais parfois, cela se produit lorsque vous devez effectuer une opération asynchrone pour chaque entrée d'un tableau ou d'une autre structure de type liste. La tentation est de faire ceci:
Exemple:
Afficher l'extrait de code
La raison pour laquelle cela ne fonctionne pas est que les rappels de
doSomethingAsync
n'ont pas encore été exécutés au moment où vous essayez d'utiliser les résultats.Donc, si vous avez un tableau (ou une liste quelconque) et que vous souhaitez effectuer des opérations asynchrones pour chaque entrée, vous avez deux options: effectuer les opérations en parallèle (chevauchement) ou en série (l'une après l'autre en séquence).
Parallèle
Vous pouvez tous les démarrer et garder une trace du nombre de rappels que vous attendez, puis utiliser les résultats lorsque vous avez reçu autant de rappels:
Exemple:
Afficher l'extrait de code
(Nous pourrions supprimer
expecting
et simplement utiliserresults.length === theArray.length
, mais cela nous laisse ouverts à la possibilité detheArray
changer pendant que les appels sont en attente ...)Remarquez comment nous utilisons le
index
fromforEach
pour enregistrer le résultat dansresults
la même position que l'entrée à laquelle il se rapporte, même si les résultats arrivent dans le désordre (car les appels asynchrones ne se terminent pas nécessairement dans l'ordre dans lequel ils ont été démarrés).Mais que faire si vous devez renvoyer ces résultats à partir d'une fonction? Comme les autres réponses l'ont souligné, vous ne pouvez pas; vous devez avoir votre fonction accepter et appeler un rappel (ou retourner une promesse ). Voici une version de rappel:
Exemple:
Afficher l'extrait de code
Ou voici une version renvoyant un à la
Promise
place:Bien sûr, si
doSomethingAsync
nous transmettions des erreurs, nous utiliserionsreject
pour rejeter la promesse lorsque nous aurons une erreur.)Exemple:
Afficher l'extrait de code
(Ou alternativement, vous pouvez faire un wrapper pour
doSomethingAsync
que retourne une promesse, puis faites ce qui suit ...)Si
doSomethingAsync
vous donne une promesse , vous pouvez utiliserPromise.all
:Si vous savez que
doSomethingAsync
cela ignorera un deuxième et un troisième argument, vous pouvez simplement le passer directement àmap
(map
appelle son rappel avec trois arguments, mais la plupart des gens n'utilisent que le premier la plupart du temps):Exemple:
Afficher l'extrait de code
Notez que
Promise.all
résout sa promesse avec un tableau des résultats de toutes les promesses que vous lui faites quand elles sont toutes résolues, ou rejette sa promesse lorsque la première des promesses que vous lui donnez rejette.Séries
Supposons que vous ne vouliez pas que les opérations soient en parallèle? Si vous souhaitez les exécuter l'une après l'autre, vous devez attendre la fin de chaque opération avant de commencer la suivante. Voici un exemple d'une fonction qui fait cela et appelle un rappel avec le résultat:
(Puisque nous effectuons le travail en série, nous pouvons simplement l'utiliser
results.push(result)
car nous savons que nous n'obtiendrons pas de résultats en désordre. Dans ce qui précède, nous aurions pu utiliserresults[index] = result;
, mais dans certains des exemples suivants, nous n'avons pas d'index utiliser.)Exemple:
Afficher l'extrait de code
(Ou, encore une fois, créez un wrapper pour
doSomethingAsync
cela vous donne une promesse et faites ce qui suit ...)Si
doSomethingAsync
vous donne une promesse, si vous pouvez utiliser la syntaxe ES2017 + (peut-être avec un transpilateur comme Babel ), vous pouvez utiliser uneasync
fonction avecfor-of
etawait
:Exemple:
Afficher l'extrait de code
Si vous ne pouvez pas encore utiliser la syntaxe ES2017 +, vous pouvez utiliser une variante de la modèle "Promise réduire" (c'est plus complexe que la promesse habituelle de réduire car nous ne transmettons pas le résultat de l'un à l'autre, mais à la place regroupant leurs résultats dans un tableau):
Exemple:
Afficher l'extrait de code
... ce qui est moins encombrant avec fonctions flèches ES2015 + :
Exemple:
Afficher l'extrait de code
la source
if (--expecting === 0)
partie du code s'il vous plaît? La version de rappel de votre solution fonctionne très bien pour moi, je ne comprends tout simplement pas comment, avec cette déclaration, vous vérifiez le nombre de réponses complétées. Je comprends que c'est juste un manque de connaissances de ma part. Y a-t-il une autre façon de faire un chèque?expecting
commence par la valeur dearray.length
, qui est le nombre de demandes que nous allons faire. Nous savons que le rappel ne sera pas appelé tant que toutes ces demandes n'auront pas été lancées. Dans le rappel,if (--expecting === 0)
fait ceci: 1. Décrémentationsexpecting
(nous avons reçu une réponse, donc nous attendons une réponse de moins) et si la valeur après la décrémentation est 0 (nous n'attendons plus de réponses), nous sommes terminé!results
n'existait pas). :-) Corrigé.Jetez un œil à cet exemple:
Comme vous pouvez le voir
getJoke
est le retour d' une résolution promesse (il est résolu lors du retourres.data.value
). Vous attendez donc que la requête $ http.get soit terminée, puis que console.log (res.joke) soit exécuté (comme un flux asynchrone normal).C'est le plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
Chemin ES6 (async - attendre)
la source
C'est l'un des endroits où la liaison de données ou le concept de magasin utilisé dans de nombreux nouveaux frameworks JavaScript fonctionnera très bien pour vous ...
Donc, si vous utilisez Angular, React ou tout autre framework qui utilise le concept de liaison de données ou de stockage de deux manières, ce problème est simplement résolu pour vous, donc en un mot, votre résultat est
undefined
à la première étape, donc vous avez obtenuresult = undefined
avant de recevoir le données, puis dès que vous obtenez le résultat, il sera mis à jour et affecté à la nouvelle valeur quelle réponse de votre appel Ajax ...Mais comment vous pouvez le faire en pur javascript ou jQuery par exemple comme vous l'avez demandé dans cette question?
Vous pouvez utiliser un rappel , une promesse et récemment observable pour le gérer pour vous, par exemple dans les promesses, nous avons une fonction comme
success()
outhen()
qui sera exécutée lorsque vos données seront prêtes pour vous, de même avec la fonction de rappel ou d' abonnement sur observable .Par exemple, dans votre cas où vous utilisez jQuery , vous pouvez faire quelque chose comme ceci:
Pour plus d'informations, étudiez les promesses et les observables qui sont de nouvelles façons de faire ces trucs asynchrones.
la source
$.ajax({url: "api/data", success: fooDone.bind(this)});
C'est un problème très courant auquel nous sommes confrontés alors que nous luttons avec les «mystères» de JavaScript. Permettez-moi d'essayer de démystifier ce mystère aujourd'hui.
Commençons par une simple fonction JavaScript:
C'est un simple appel de fonction synchrone (où chaque ligne de code est «terminée avec son travail» avant la suivante dans l'ordre), et le résultat est le même que prévu.
Ajoutons maintenant un peu de torsion, en introduisant peu de retard dans notre fonction, afin que toutes les lignes de code ne soient pas «finies» dans l'ordre. Ainsi, il émulera le comportement asynchrone de la fonction:
Alors voilà, ce retard vient de casser la fonctionnalité que nous attendions! Mais que s'est-il passé exactement? Eh bien, c'est en fait assez logique si vous regardez le code. la fonction
foo()
, lors de son exécution, ne retourne rien (ainsi la valeur retournée estundefined
), mais elle démarre un temporisateur, qui exécute une fonction après 1s pour retourner 'wohoo'. Mais comme vous pouvez le voir, la valeur attribuée à la barre est la substance immédiatement renvoyée par foo (), qui n'est rien, c'est-à-dire justeundefined
.Alors, comment abordons-nous ce problème?
Demandons à notre fonction une PROMESSE . La promesse est vraiment ce qu'elle signifie: cela signifie que la fonction vous garantit de fournir toute sortie qu'elle obtiendra à l'avenir. voyons donc cela en action pour notre petit problème ci-dessus:
Ainsi, le résumé est - pour aborder les fonctions asynchrones comme les appels basés sur ajax, etc., vous pouvez utiliser une promesse de
resolve
valeur (que vous avez l'intention de renvoyer). Ainsi, en bref, vous résolvez la valeur au lieu de retourner , dans les fonctions asynchrones.MISE À JOUR (promesses avec async / attente)
Outre l'utilisation
then/catch
pour travailler avec des promesses, il existe une autre approche. L'idée est de reconnaître une fonction asynchrone , puis d' attendre que les promesses soient résolues, avant de passer à la ligne de code suivante. C'est toujourspromises
sous le capot, mais avec une approche syntaxique différente. Pour rendre les choses plus claires, vous pouvez trouver une comparaison ci-dessous:puis / catch version:
version asynchrone / en attente:
la source
Une autre approche pour renvoyer une valeur à partir d'une fonction asynchrone consiste à passer un objet qui stockera le résultat de la fonction asynchrone.
Voici un exemple de la même:
J'utilise le
result
objet pour stocker la valeur pendant l'opération asynchrone. Cela permet au résultat d'être disponible même après le travail asynchrone.J'utilise beaucoup cette approche. Je serais intéressé de savoir dans quelle mesure cette approche fonctionne lorsque le câblage du résultat via des modules consécutifs est impliqué.
la source
result
. Cela fonctionne car vous lisez la variable une fois la fonction asynchrone terminée.Bien que les promesses et les rappels fonctionnent bien dans de nombreuses situations, il est difficile d'exprimer quelque chose comme:
Vous finiriez par passer
async1
; vérifiez s'ilname
n'est pas défini ou non et appelez le rappel en conséquence.Bien que cela soit correct dans de petits exemples, cela devient ennuyeux lorsque de nombreux cas similaires et une gestion des erreurs sont impliqués.
Fibers
aide à résoudre le problème.Vous pouvez consulter le projet ici .
la source
async-await
si vous utilisez certaines des dernières versions de node. Si quelqu'un est bloqué avec des versions plus anciennes, il peut utiliser cette méthode.L'exemple suivant que j'ai écrit montre comment
Cet exemple de travail est autonome. Il définira un objet de requête simple qui utilise l'
XMLHttpRequest
objet window pour effectuer des appels. Il définira une fonction simple pour attendre qu'un tas de promesses soient terminées.Le contexte. L'exemple interroge le point de terminaison de l' API Web Spotify afin de rechercher des
playlist
objets pour un ensemble donné de chaînes de requête:Pour chaque élément, une nouvelle promesse déclenchera un bloc -
ExecutionBlock
, analysera le résultat, planifiera un nouvel ensemble de promesses basé sur le tableau de résultats, c'est-à-dire une liste d'user
objets Spotify et exécutera le nouvel appel HTTP deExecutionProfileBlock
manière asynchrone.Vous pouvez alors voir une structure Promise imbriquée, qui vous permet de générer plusieurs appels HTTP imbriqués complètement et asynchrones, et de joindre les résultats de chaque sous-ensemble d'appels
Promise.all
.REMARQUE Les
search
API Spotify récentes nécessiteront un jeton d'accès à spécifier dans les en-têtes de demande:Ainsi, pour exécuter l'exemple suivant, vous devez placer votre jeton d'accès dans les en-têtes de demande:
J'ai longuement discuté de cette solution ici .
la source
En bref, vous devez implémenter un rappel comme celui-ci:
la source
Réponse 2017: vous pouvez désormais faire exactement ce que vous voulez dans chaque navigateur et nœud actuel
C'est assez simple:
Voici une version de travail de votre code:
attendre est pris en charge dans tous les navigateurs actuels et le nœud 8
la source
await/async
Le navigateur peut être divisé en trois parties:
1) Boucle d'événement
2) API Web
3) File d'attente des événements
La boucle d'événement fonctionne pour toujours, c'est-à-dire le type de boucle infinie. pour le suivant après que le premier soit exécuté. Cela signifie que l'exécution d'une fonction ne démarre pas tant que la fonction avant qu'elle ne soit en file d'attente ne soit exécutée dans la boucle d'événement.
Maintenant, pensons que nous avons poussé deux fonctions dans une file d'attente, l'une pour obtenir des données du serveur et une autre utilise ces données. Nous avons d'abord poussé la fonction serverRequest () dans la file d'attente puis la fonction utiliséData (). La fonction serverRequest va dans la boucle d'événements et fait un appel au serveur car nous ne savons jamais combien de temps il faudra pour obtenir des données du serveur, donc ce processus devrait prendre du temps et nous avons donc occupé notre boucle d'événements en suspendant ainsi notre page, c'est là que le Web L'API entre dans le rôle, elle prend cette fonction de la boucle d'événements et traite du fait que le serveur rend la boucle d'événements libre afin que nous puissions exécuter la fonction suivante de la file d'attente.La prochaine fonction dans la file d'attente est utiliseData () qui va en boucle mais en raison de l'absence de données disponibles, elle va le gaspillage et l'exécution de la fonction suivante se poursuivent jusqu'à la fin de la file d'attente (c'est ce qu'on appelle l'appel asynchrone, c'est-à-dire que nous pouvons faire autre chose jusqu'à ce que nous obtenions des données).
Supposons que notre fonction serverRequest () contienne une instruction return dans un code, lorsque nous récupérons les données du serveur, l'API Web les place dans la file d'attente à la fin de la file d'attente. Comme il est poussé à la fin de la file d'attente, nous ne pouvons pas utiliser ses données car il n'y a plus de fonction dans notre file d'attente pour utiliser ces données. Ainsi, il n'est pas possible de renvoyer quelque chose depuis un appel asynchrone.
Ainsi, la solution à cela est un rappel ou une promesse .
Une image d'une des réponses ici, explique correctement l'utilisation du rappel ... Nous donnons notre fonction (fonction utilisant les données renvoyées par le serveur) à la fonction serveur appelant.
Dans mon code, il est appelé
Rappel Javscript.info
la source
Vous pouvez utiliser cette bibliothèque personnalisée (écrite à l'aide de Promise) pour effectuer un appel à distance.
Exemple d'utilisation simple:
la source
Une autre solution consiste à exécuter du code via l'exécuteur séquentiel nsynjs .
Si la fonction sous-jacente est promise
nsynjs évaluera toutes les promesses séquentiellement et mettra le résultat de la promesse dans la
data
propriété:Si la fonction sous-jacente n'est pas promise
Étape 1. Enveloppez la fonction avec rappel dans un wrapper prenant en charge nsynjs (s'il a une version promise, vous pouvez ignorer cette étape):
Étape 2. Mettez la logique synchrone en fonction:
Étape 3. Exécutez la fonction de manière synchrone via nsynjs:
Nsynjs évaluera tous les opérateurs et expressions étape par étape, interrompant l'exécution au cas où le résultat d'une fonction lente n'est pas prêt.
Plus d'exemples ici: https://github.com/amaksr/nsynjs/tree/master/examples
la source
ECMAScript 6 dispose de «générateurs» qui vous permettent de programmer facilement dans un style asynchrone.
Pour exécuter le code ci-dessus, procédez comme suit:
Si vous devez cibler des navigateurs qui ne prennent pas en charge ES6, vous pouvez exécuter le code via Babel ou le compilateur de fermeture pour générer ECMAScript 5.
Les rappels
...args
sont enveloppés dans un tableau et déstructurés lorsque vous les lisez afin que le modèle puisse gérer les rappels qui ont plusieurs arguments. Par exemple avec le nœud fs :la source
Voici quelques approches pour travailler avec des requêtes asynchrones:
Exemple: implémentation différée jQuery pour travailler avec plusieurs requêtes
la source
Nous nous trouvons dans un univers qui semble progresser selon une dimension que nous appelons «temps». Nous ne comprenons pas vraiment l'heure, 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 une dimension importante. Certaines choses devraient se produire à l'avenir. Ensuite, d'autres choses doivent se produire après que ces premières choses se sont finalement produites. C'est la notion de base appelée "asynchronicité". Dans notre monde de plus en plus en réseau, le cas le plus courant d'asynchronicité attend qu'un système distant réponde à une demande.
Prenons un exemple. Vous appelez le laitier et commandez du lait. Quand il vient, vous voulez le mettre dans votre café. Vous ne pouvez pas mettre le lait dans votre café en ce moment, car il n'est pas encore là. Vous devez attendre qu'il vienne avant de le mettre dans votre café. En d'autres termes, les éléments suivants ne fonctionneront pas:
Parce que JS n'a aucun moyen de savoir qu'il doit attendre pour
order_milk
terminer avant son exécutionput_in_coffee
. En d'autres termes, il ne sait pas queorder_milk
c'est asynchrone - c'est quelque chose qui ne produira pas de lait avant un certain temps. JS et d'autres langages déclaratifs exécutent une instruction après l'autre sans attendre.L'approche JS classique de ce problème, tirant parti du fait que JS prend en charge les fonctions en tant qu'objets de première classe pouvant être transmis, consiste à transmettre une fonction en tant que paramètre à la demande asynchrone, qu'elle invoquera ensuite lorsqu'elle sera terminée. sa tâche dans le futur. C'est l'approche du "rappel". Cela ressemble à ceci:
order_milk
démarre, commande le lait, puis, quand et seulement quand il arrive, il invoqueput_in_coffee
.Le problème avec cette approche de rappel est qu'elle pollue la sémantique normale d'une fonction rapportant son résultat avec
return
; à la place, les fonctions ne doivent pas rapporter leurs résultats en appelant un rappel donné en paramètre. En outre, cette approche peut rapidement devenir difficile à gérer lorsqu'il s'agit de séquences d'événements plus longues. Par exemple, disons que je veux attendre que le lait soit mis dans le café, puis ensuite seulement effectuer une troisième étape, à savoir boire le café. Je finis par avoir besoin d'écrire quelque chose comme ça:où je passe à la
put_in_coffee
fois au lait pour y mettre, et aussi à l'action (drink_coffee
) à exécuter une fois le lait introduit. Un tel code devient difficile à écrire, à lire et à déboguer.Dans ce cas, nous pourrions réécrire le code de la question comme suit:
Entrez des promesses
Telle était la motivation de la notion de «promesse», qui est un type particulier de valeur qui représente une sorte de résultat futur ou asynchrone . Cela peut représenter quelque chose qui s'est déjà produit, ou qui se produira à l'avenir, ou qui pourrait ne jamais se produire du tout. Les promesses ont une seule méthode, nommée
then
, à laquelle vous passez une action à exécuter lorsque le résultat que la promesse représente a été réalisé.Dans le cas de notre lait et de notre café, nous prévoyons
order_milk
de retourner une promesse pour le lait arrivant, puis spécifionsput_in_coffee
comme unethen
action, comme suit:Un avantage de ceci est que nous pouvons les enchaîner pour créer des séquences d'occurrences futures ("chaînage"):
Appliquons des promesses à votre problème particulier. Nous allons envelopper notre logique de requête dans une fonction, qui retourne une promesse:
En fait, tout ce que nous avons fait est ajouté un
return
à l'appel à$.ajax
. Cela fonctionne parce que jQuery$.ajax
retourne déjà une sorte de chose semblable à une promesse. (En pratique, sans entrer dans les détails, nous préférerions encapsuler cet appel afin de renvoyer une vraie promesse, ou utiliser une alternative à$.ajax
cela.) Maintenant, si nous voulons charger le fichier et attendre qu'il se termine et alors faites quelque chose, on peut simplement direpar exemple,
Lorsque nous utilisons des promesses, nous
then
finissons par transmettre de nombreuses fonctions , il est donc souvent utile d'utiliser les fonctions fléchées de style ES6 plus compactes:Le
async
mot cléMais il y a toujours quelque chose de vaguement insatisfaisant à devoir écrire du code dans un sens s'il est synchrone et d'une manière assez différente s'il est asynchrone. Pour synchrone, nous écrivons
mais s'il
a
est asynchrone, avec des promesses, nous devons écrireCi-dessus, nous avons dit: "JS n'a aucun moyen de savoir qu'il doit attendre la fin du premier appel avant d'exécuter le second". Ce ne serait pas bien s'il y avait un moyen de dire ça à JS? Il s'avère qu'il existe - le
await
mot - clé, utilisé à l'intérieur d'un type spécial de fonction appelé fonction "asynchrone". Cette fonctionnalité fait partie de la prochaine version d'ES mais est déjà disponible dans des transpilers tels que Babel, étant donné les bons préréglages. Cela nous permet d'écrire simplementDans votre cas, vous pourriez écrire quelque chose comme
la source
Réponse courte : votre
foo()
méthode renvoie immédiatement, tandis que l'$ajax()
appel s'exécute de manière asynchrone après le retour de la fonction . Le problème est alors de savoir comment ou où stocker les résultats récupérés par l'appel asynchrone une fois qu'il revient.Plusieurs solutions ont été données dans ce fil. Le moyen le plus simple consiste peut-être à passer un objet à la
foo()
méthode et à stocker les résultats dans un membre de cet objet une fois l'appel asynchrone terminé.Notez que l'appel à
foo()
ne retournera toujours rien d'utile. Cependant, le résultat de l'appel asynchrone sera désormais stocké dansresult.response
.la source
Utilisez une
callback()
fonction à l'intérieur dufoo()
succès. Essayez de cette façon. C'est simple et facile à comprendre.la source
La question était:
qui PEUT être interprété comme:
La solution consistera à éviter les rappels et à utiliser une combinaison de promesses et d' async / wait .
Je voudrais donner un exemple de demande Ajax.
(Bien qu'il puisse être écrit en Javascript, je préfère l'écrire en Python et le compiler en Javascript à l'aide de Transcrypt . Ce sera assez clair.)
Permet d'abord d'activer l'utilisation de JQuery, d'avoir
$
disponible en tant queS
:Définissez une fonction qui renvoie une promesse , dans ce cas un appel Ajax:
Utilisez le code asynchrone comme s'il était synchrone :
la source
Utiliser Promise
La réponse la plus parfaite à cette question est d'utiliser
Promise
.Usage
Mais attendez...!
Il y a un problème avec l'utilisation des promesses!
Pourquoi devrions-nous utiliser notre propre promesse personnalisée?
J'utilisais cette solution depuis un certain temps jusqu'à ce que je comprenne qu'il y a une erreur dans les anciens navigateurs:
J'ai donc décidé d'implémenter ma propre classe Promise pour ES3 aux compilateurs js ci-dessous si elle n'est pas définie. Ajoutez simplement ce code avant votre code principal, puis utilisez Promise en toute sécurité!
la source
Bien sûr, il existe de nombreuses approches comme la demande synchrone, promis, mais d'après mon expérience, je pense que vous devriez utiliser l'approche de rappel. C'est naturel au comportement asynchrone de Javascript. Ainsi, votre extrait de code peut être réécrit un peu différemment:
la source
Plutôt que de vous lancer du code, deux concepts sont essentiels pour comprendre comment JS gère les rappels et l'asynchronicité. (Est-ce que c'est un mot?)
Le modèle de boucle d'événement et de concurrence
Il y a trois choses dont vous devez être conscient; La queue; la boucle d'événements et la pile
En termes larges et simplistes, la boucle d'événement est comme le chef de projet, elle écoute constamment toutes les fonctions qui veulent s'exécuter et communique entre la file d'attente et la pile.
Une fois qu'il reçoit un message pour exécuter quelque chose, il l'ajoute à la file d'attente. La file d'attente est la liste des choses en attente d'exécution (comme votre requête AJAX). imaginez-le comme ceci:
Lorsqu'un de ces messages va s'exécuter, il fait apparaître le message dans la file d'attente et crée une pile, la pile est tout ce dont JS a besoin pour exécuter l'instruction du message. Donc, dans notre exemple, on lui dit d'appeler
foobarFunc
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énements se déplacera ensuite vers la prochaine chose dans la file d'attente (ou écoutera les messages)L'élément clé ici est l'ordre d'exécution. C'est
QUAND est quelque chose qui va courir
Lorsque vous passez un appel à l'aide d'AJAX à un correspondant externe ou exécutez un code asynchrone (un setTimeout par exemple), Javascript dépend d'une réponse avant de pouvoir continuer.
La grande question est de savoir quand obtiendra-t-elle la réponse? La réponse est que nous ne savons pas - donc la boucle d'événements attend que ce message dise "hey run me". Si JS attendait ce message de manière synchrone, votre application se bloquerait et elle serait nulle. JS continue donc d'exécuter l'élément suivant dans la file d'attente en attendant que le message soit ajouté à la file d'attente.
C'est pourquoi, avec la fonctionnalité asynchrone, nous utilisons des choses appelées rappels . C'est un peu comme une promesse littéralement. Comme dans Je promets de retourner quelque chose à un moment donné, jQuery utilise des rappels spécifiques appelés
deffered.done
deffered.fail
etdeffered.always
(entre autres). Vous pouvez tous les voir iciDonc, ce que vous devez faire, c'est passer une fonction qui est promise à exécuter à un moment donné avec des données qui lui sont transmises.
Parce qu'un rappel n'est pas exécuté immédiatement mais ultérieurement, il est important de transmettre la référence à la fonction non exécutée. donc
donc la plupart du temps (mais pas toujours) vous ne passerez
foo
pasfoo()
J'espère que cela aura un certain sens. Lorsque vous rencontrez des choses comme celle-ci qui semblent déroutantes - je vous recommande fortement de lire la documentation entièrement pour au moins la comprendre. Cela fera de vous un bien meilleur développeur.
la source
En utilisant ES2017, vous devriez avoir ceci comme déclaration de fonction
Et l'exécuter comme ça.
Ou la syntaxe Promise
la source