J'ai une fonction foo
qui fait une demande Ajax. Comment puis-je retourner la réponse de foo
?
J'ai essayé de renvoyer la valeur à partir du rappel success
, ainsi que d'assigner la réponse à une variable locale de la fonction et de la renvoyer, mais aucun de ces moyens ne renvoie la réponse.
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response; // <- I tried that one as well
}
});
return result;
}
var result = foo(); // It always ends up being `undefined`.
→ Pour une explication plus générale du comportement asynchrone avec différents exemples, veuillez consulter Pourquoi ma variable est-elle inchangée après que je la modifie à l'intérieur d'une fonction? - Référence de code asynchrone
→ Si vous comprenez déjà le problème, passez aux solutions possibles ci-dessous.
Le A en Ajax signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple, _$.ajax
_ retourne immédiatement et l'instruction suivante, _return result;
_, est exécutée avant que la fonction que vous avez transmise en tant que rappel success
ne soit même appelée.
Voici une analogie qui, espérons-le, rend la différence entre flux synchrone et asynchrone plus claire:
Imaginez que vous appeliez un ami et lui demandiez de rechercher quelque chose pour vous. Bien que cela puisse prendre un certain temps, vous attendez au téléphone et fixez l'espace, jusqu'à ce que votre ami vous donne la réponse dont vous aviez besoin.
La même chose se produit lorsque vous effectuez un appel de fonction contenant le code "normal":
_function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
_
Bien que l'exécution de findItem
puisse prendre beaucoup de temps, tout code venant après var item = findItem();
doit être wait jusqu'à ce que la fonction retourne le résultat.
Vous appelez à nouveau votre ami pour la même raison. Mais cette fois, vous lui dites que vous êtes pressé et qu'il devrait vous rappeler sur votre téléphone mobile. Vous raccrochez, quittez la maison et faites ce que vous aviez prévu de faire. Une fois que votre ami vous rappelle, vous traitez avec les informations qu'il vous a données.
C'est exactement ce qui se passe lorsque vous faites une demande Ajax.
_findItem(function(item) {
// Do something with item
});
doSomethingElse();
_
Au lieu d'attendre la réponse, l'exécution continue immédiatement et l'instruction après l'exécution de l'appel Ajax. Pour obtenir la réponse éventuellement, vous indiquez une fonction à appeler une fois la réponse reçue, un callback (remarquez quelque chose? call back ?). Toute instruction venant après cet appel est exécutée avant l'appel du rappel.
Adoptez la nature asynchrone de JavaScript! Bien que certaines opérations asynchrones fournissent des contreparties synchrones (tout comme "Ajax"), il est généralement déconseillé de les utiliser, notamment dans les cas suivants: contexte du navigateur.
Pourquoi est-ce mauvais, demandez-vous?
JavaScript s'exécute dans le thread d'interface utilisateur du navigateur et tout processus long verrouille l'interface utilisateur, ce qui la rend inactive. En outre, le temps d'exécution de JavaScript est limité et le navigateur demande à l'utilisateur s'il souhaite ou non poursuivre l'exécution.
Tout cela constitue une très mauvaise expérience utilisateur. L'utilisateur ne pourra pas dire si tout fonctionne bien ou non. De plus, l’effet sera pire pour les utilisateurs avec une connexion lente.
Dans ce qui suit, nous examinerons trois solutions différentes qui se construisent les unes sur les autres:
async/await
_ (ES2017 +, disponible dans les navigateurs plus anciens si vous utilisez un transpiler ou un régénérateur)then()
(ES2015 +, disponible dans les navigateurs plus anciens si vous utilisez l'une des nombreuses bibliothèques de promesses)Tous les trois sont disponibles dans les navigateurs actuels, ainsi que sur le noeud 7 +.
async/await
La version ECMAScript publiée en 2017 a introduit support au niveau de la syntaxe pour les fonctions asynchrones. À l'aide de async
et await
, vous pouvez écrire de manière asynchrone dans un "style synchrone". Le code est toujours asynchrone, mais il est plus facile à lire/à comprendre.
_async/await
_ s'appuie sur les promesses: une fonction async
renvoie toujours une promesse. await
"déballe" une promesse et aboutit à la valeur avec laquelle la promesse a été résolue ou génère une erreur si la promesse est rejetée.
Important: Vous ne pouvez utiliser que await
dans une fonction async
. À l'heure actuelle, await
de niveau supérieur n'est pas encore pris en charge. Vous devrez donc peut-être créer un IIFE asynchrone ( Expression de fonction immédiate ) pour démarrer un contexte async
.
Vous pouvez en savoir plus sur async
et await
sur MDN.
Voici un exemple qui se base sur le délai ci-dessus:
_// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
_
Les versions actuelles du navigateur et du noeud prennent en charge _async/await
_. Vous pouvez également prendre en charge des environnements plus anciens en transformant votre code en ES5 à l'aide du régénérateur (ou d'outils utilisant ce régénérateur, tel que Babel ).
Un rappel est simplement une fonction transmise à une autre fonction. Cette autre fonction peut appeler la fonction transmise à chaque fois qu'elle est prête. Dans le contexte d'un processus asynchrone, le rappel sera appelé chaque fois que le processus asynchrone est terminé. Habituellement, le résultat est transmis au rappel.
Dans l'exemple de la question, vous pouvez faire en sorte que foo
accepte un rappel et l'utilise comme rappel success
. Donc ça
_var result = foo();
// Code that depends on 'result'
_
devient
_foo(function(result) {
// Code that depends on 'result'
});
_
Ici nous avons défini la fonction "inline" mais vous pouvez passer n'importe quelle référence de fonction:
_function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
_
foo
lui-même est défini comme suit:
_function foo(callback) {
$.ajax({
// ...
success: callback
});
}
_
callback
fera référence à la fonction que nous passons à foo
lorsque nous l'appelons et nous la transmettons simplement à success
. C'est à dire. une fois que la demande Ajax a abouti, _$.ajax
_ appelle callback
et transmet la réponse au rappel (auquel on peut faire référence avec result
, puisque c'est ainsi que nous avons défini le rappel).
Vous pouvez également traiter la réponse avant de la transmettre au rappel:
_function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
_
Il est plus facile d'écrire du code à l'aide de rappels que cela puisse paraître. Après tout, JavaScript dans le navigateur est fortement basé sur les événements (événements DOM). Recevoir la réponse Ajax n'est rien d'autre qu'un événement.
Des problèmes peuvent survenir lorsque vous devez travailler avec du code tiers, mais la plupart des problèmes peuvent être résolus simplement en réfléchissant au processus d'application.
L'API Promise est une nouvelle fonctionnalité d'ECMAScript 6 (ES2015), mais elle dispose déjà d'un bon support du navigateur . De nombreuses bibliothèques implémentent également l’API standard Promises et fournissent des méthodes supplémentaires pour faciliter l’utilisation et la composition de fonctions asynchrones (par exemple bluebird ).
Les promesses sont des conteneurs pour les valeurs future . Lorsque la promesse reçoit la valeur (c'est résolu ) ou lorsqu'elle est annulée ( rejeté ), il en avertit tous les "" "auditeurs" qui veulent accéder à cette valeur.
L'avantage par rapport aux rappels en clair est qu'ils vous permettent de découpler votre code et qu'ils sont plus faciles à composer.
Voici un exemple simple d'utilisation d'une promesse:
_function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
delay()
.then(function(v) { // `delay` returns a promise
console.log(v); // Log the value once it is resolved
})
.catch(function(v) {
// Or do something else if it is rejected
// (it would not happen in this example, since `reject` is not called).
});
_
Appliqué à notre appel Ajax, nous pourrions utiliser des promesses comme celle-ci:
_function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
_
Décrire tous les avantages offerts par promesse dépasse le cadre de cette réponse, mais si vous écrivez un nouveau code, vous devez l’envisager sérieusement. Ils fournissent une grande abstraction et séparation de votre code.
Plus d'informations sur les promesses: HTML5 roches - Promesses JavaScript
Les objets différés sont des implémentations personnalisées de promesses par jQuery (avant la normalisation de l'API Promise). Ils se comportent presque comme des promesses mais exposent une API légèrement différente.
Chaque méthode Ajax de jQuery renvoie déjà un "objet différé" (en fait une promesse d'objet différé) que vous pouvez simplement renvoyer à partir de votre fonction:
_function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
_
Gardez à l'esprit que les promesses et les objets différés ne sont que conteneurs pour une valeur future, ils ne sont pas la valeur elle-même. Par exemple, supposons que vous ayez:
_function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
_
Ce code comprend mal les problèmes d'asynchronie ci-dessus. Plus précisément, $.ajax()
ne gèle pas le code pendant la vérification de la page '/ mot de passe' sur votre serveur. Il envoie une requête au serveur et pendant l'attente, renvoie immédiatement un objet jQuery Ajax Deferred, et non la réponse de le serveur. Cela signifie que l'instruction if
obtiendra toujours cet objet différé, la traitera comme true
et procédera comme si l'utilisateur était connecté. Mauvais.
Mais la solution est facile:
_checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
_
Comme je l'ai mentionné, certaines (!) Opérations asynchrones ont des contreparties synchrones. Je ne préconise pas leur utilisation, mais par souci d'exhaustivité, voici comment procéder pour effectuer un appel synchrone:
Si vous utilisez directement un objet XMLHTTPRequest
, transmettez false
en tant que troisième argument à .open
.
Si vous utilisez jQuery , vous pouvez définir l’option async
sur false
. Notez que cette option est obsolète depuis jQuery 1.8. Vous pouvez alors toujours utiliser un rappel success
ou accéder à la propriété responseText
de l'objet jqXHR :
_function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
_
Si vous utilisez une autre méthode Ajax jQuery, telle que _$.get
_, _$.getJSON
_, etc., vous devez la changer en _$.ajax
_ (puisque vous ne pouvez transmettre que les paramètres de configuration à _$.ajax
_).
Attention! Il n'est pas possible de faire une demande synchrone JSONP . JSONP, de par sa nature même, est toujours asynchrone (une raison de plus pour ne même pas envisager cette option).
Votre code devrait être quelque chose dans le sens de ceci:
_function foo() {
var httpRequest = new XMLHttpRequest();
httpRequest.open('GET', "/echo/json");
httpRequest.send();
return httpRequest.responseText;
}
var result = foo(); // always ends up being 'undefined'
_
Felix Kling a très bien écrit les réponses aux personnes utilisant jQuery pour AJAX. J'ai décidé de proposer une solution de remplacement aux personnes qui ne le sont pas.
Ceci est un court résumé de "Explication du problème" de l'autre réponse. Si vous n'êtes pas sûr après avoir lu ceci, lisez-le.
Le A dans AJAX signifie asynchrone . Cela signifie que l'envoi de la demande (ou plutôt la réception de la réponse) est retiré du flux d'exécution normal. Dans votre exemple, .send
est renvoyé immédiatement et l'instruction suivante, _return result;
_, est exécutée avant que la fonction transmise en tant que success
callback n'ait été appelée.
Cela signifie que lorsque vous revenez, l'écouteur que vous avez défini ne s'est pas encore exécuté, ce qui signifie que la valeur que vous renvoyez n'a pas encore été définie.
Voici une analogie simple
_function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
_
La valeur de a
renvoyée est undefined
puisque la partie _a=5
_ n'a pas encore été exécutée. AJAX agit comme ceci: vous renvoyez la valeur avant que le serveur ait la chance de dire à votre navigateur quelle est cette valeur.
Une solution possible à ce problème consiste à coder de manière réactive , en indiquant à votre programme quoi faire lorsque le calcul est terminé.
_function onComplete(a){ // When the code completes, do this
alert(a);
}
function getFive(whenDone){
var a;
setTimeout(function(){
a=5;
whenDone(a);
},10);
}
_
Ceci s'appelle CPS . Fondamentalement, nous passons getFive
une action à exécuter à la fin. Nous expliquons à notre code comment réagir à la fin d’un événement (comme notre appel AJAX, ou dans ce cas, le délai ).
L'utilisation serait:
_getFive(onComplete);
_
Ce qui devrait alerter "5" sur l'écran. (Violon) .
Il existe fondamentalement deux façons de résoudre ce problème:
Quant à AJAX synchrone, ne le faites pas! La réponse de Felix soulève des arguments convaincants sur la raison pour laquelle c'est une mauvaise idée. Pour résumer, cela gèlera le navigateur de l'utilisateur jusqu'à ce que le serveur renvoie la réponse et créera une très mauvaise expérience utilisateur. Voici un autre court résumé tiré de MDN expliquant pourquoi:
XMLHttpRequest prend en charge les communications synchrones et asynchrones. En règle générale, toutefois, les demandes asynchrones doivent être préférées aux demandes synchrones pour des raisons de performances.
En bref, les requêtes synchrones bloquent l'exécution du code ... ... cela peut causer de sérieux problèmes ...
Si vous devez le faire, vous pouvez passer un drapeau: voici comment:
_var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false); // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {// That's HTTP for 'ok'
console.log(request.responseText);
}
_
Laissez votre fonction accepter un rappel. Dans l'exemple, le code foo
peut être configuré pour accepter un rappel. Nous dirons à notre code comment réagir à la fin de foo
.
Alors:
_var result = foo();
// code that depends on `result` goes here
_
Devient:
_foo(function(result) {
// code that depends on `result`
});
_
Ici, nous avons passé une fonction anonyme, mais nous pourrions tout aussi facilement passer une référence à une fonction existante, en lui donnant l'aspect suivant:
_function myHandler(result) {
// code that depends on `result`
}
foo(myHandler);
_
Pour plus de détails sur la manière dont ce type de conception de rappel est effectué, consultez la réponse de Felix.
Maintenant, définissons foo lui-même pour agir en conséquence
_function foo(callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onload = function(){ // when the request is loaded
callback(httpRequest.responseText);// we're calling our method
};
httpRequest.open('GET', "/echo/json");
httpRequest.send();
}
_
Nous avons maintenant forcé notre fonction foo à accepter une action à exécuter lorsque AJAX se termine avec succès. Nous pouvons l'étendre davantage en vérifiant si le statut de la réponse n'est pas 200 et en agissant en conséquence (création d'un gestionnaire d'échec, etc.). Résoudre efficacement notre problème.
Si vous avez encore du mal à comprendre cela lisez le AJAX Guide de démarrage rapide sur MDN.
XMLHttpRequest 2 (lisez tout d'abord les réponses de Benjamin Gruenbaum & Felix Kling )
Si vous n'utilisez pas jQuery et que vous voulez un Nice short XMLHttpRequest 2 qui fonctionne sur les navigateurs modernes ainsi que sur les navigateurs mobiles, je suggère de l'utiliser de cette façon:
function ajax(a, b, c){ // URL, callback, just a placeholder
c = new XMLHttpRequest;
c.open('GET', a);
c.onload = b;
c.send()
}
Comme vous pouvez le voir:
Il existe deux manières d'obtenir la réponse de cet appel Ajax (trois à l'aide du nom XMLHttpRequest var):
Le plus simple:
this.response
Ou si pour une raison quelconque vous bind()
le rappel à une classe:
e.target.response
Exemple:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
Ou (celle ci-dessus est meilleure, les fonctions anonymes sont toujours un problème):
ajax('URL', function(e){console.log(this.response)});
Rien de plus facile.
Maintenant, certaines personnes diront probablement qu'il vaut mieux utiliser onreadystatechange ou même le nom de variable XMLHttpRequest. C'est faux.
Départ fonctionnalités avancées de XMLHttpRequest
Il supportait tous les * navigateurs modernes. Et je peux confirmer que j'utilise cette approche puisque XMLHttpRequest 2 existe. Je n'ai jamais eu aucun type de problème sur tous les navigateurs que j'utilise.
onreadystatechange n'est utile que si vous souhaitez obtenir les en-têtes sur l'état 2.
L'utilisation du nom de variable XMLHttpRequest
est une autre grosse erreur, car vous devez exécuter le rappel à l'intérieur des fermetures onload/oreadystatechange, sinon vous l'avez perdu.
Maintenant, si vous voulez quelque chose de plus complexe en utilisant post et FormData, vous pouvez facilement étendre cette fonction:
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.send(d||null)
}
Encore une fois ... c'est une fonction très courte, mais elle reçoit et publie.
Exemples d'utilisation:
x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data
Ou passez un élément de formulaire complet (document.getElementsByTagName('form')[0]
):
var fd = new FormData(form);
x(url, callback, 'post', fd);
Ou définissez des valeurs personnalisées:
var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
Comme vous pouvez le constater, je n'ai pas implémenté la synchronisation ... c'est une mauvaise chose.
Cela dit, pourquoi ne pas le faire facilement?
Comme mentionné dans le commentaire, l'utilisation de error && synchrone fait complètement éclater le point de la réponse. Quel est le bon moyen d'utiliser correctement Ajax?
Gestionnaire d'erreur
function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
c = new XMLHttpRequest;
c.open(e||'get', a);
c.onload = b;
c.onerror = error;
c.send(d||null)
}
function error(e){
console.log('--Error--', this.type);
console.log('this: ', this);
console.log('Event: ', e)
}
function displayAjax(e){
console.log(e, this);
}
x('WRONGURL', displayAjax);
Dans le script ci-dessus, vous avez un gestionnaire d’erreur qui est défini de manière statique afin de ne pas compromettre la fonction. Le gestionnaire d'erreurs peut également être utilisé pour d'autres fonctions.
Cependant, pour réellement obtenir une erreur, le moyen uniquement consiste à écrire une URL incorrecte, auquel cas chaque navigateur génère une erreur.
Les gestionnaires d'erreurs sont peut-être utiles si vous définissez des en-têtes personnalisés, définissez le responseType sur le tampon de tableau blob ou autre chose ...
Même si vous passez "POSTAPAPAP" comme méthode, cela ne produira pas d'erreur.
Même si vous transmettez 'fdggdgilfdghfldj' en tant que formdata, cela ne produira pas d'erreur.
Dans le premier cas, l'erreur est à l'intérieur de la displayAjax()
sous this.statusText
en tant que Method not Allowed
.
Dans le second cas, cela fonctionne simplement. Vous devez vérifier côté serveur si vous avez transmis les bonnes données de publication.
inter-domaine non autorisé génère une erreur automatiquement.
Dans la réponse d'erreur, il n'y a pas de codes d'erreur.
Il n'y a que le this.type
qui est réglé sur erreur.
Pourquoi ajouter un gestionnaire d’erreurs si vous n’avez aucun contrôle sur les erreurs? La plupart des erreurs sont renvoyées à l'intérieur dans la fonction de rappel displayAjax()
.
Donc: pas besoin de vérification d'erreur si vous êtes capable de copier et coller l'URL correctement. ;)
PS: En tant que premier test, j’ai écrit x ('x', displayAjax) ..., et il a totalement obtenu une réponse ... ??? J'ai donc vérifié le dossier dans lequel se trouve le code HTML et il y avait un fichier appelé 'x.xml'. Ainsi, même si vous oubliez l'extension de votre fichier, XMLHttpRequest 2 LE TROUVERA . J'ai raté
Lire un fichier synchrone
Ne faites pas ça.
Si vous souhaitez bloquer le navigateur pendant un moment, chargez un fichier synchrone Nice big .txt
.
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
Maintenant tu peux faire
var res = omg('thisIsGonnaBlockThePage.txt');
Il n'y a pas d'autre moyen de faire cela de manière non asynchrone. (Oui, avec la boucle setTimeout ... mais sérieusement?)
Un autre point est ... si vous travaillez avec des API ou simplement avec les fichiers de votre propre liste ou quoi que ce soit, vous utilisez toujours des fonctions différentes pour chaque requête ...
Seulement si vous avez une page où vous chargez toujours le même XML/JSON ou tout ce dont vous n'avez besoin que d'une seule fonction. Dans ce cas, modifiez un peu la fonction Ajax et remplacez b par votre fonction spéciale.
Les fonctions ci-dessus sont pour une utilisation de base.
Si vous voulez étendre la fonction ...
Oui, vous pouvez.
J'utilise beaucoup d'API et l'une des premières fonctions que j'intègre dans chaque page HTML est la première fonction Ajax dans cette réponse, avec uniquement GET ...
Mais vous pouvez faire beaucoup de choses avec XMLHttpRequest 2:
J'ai créé un gestionnaire de téléchargement (utilisant des plages des deux côtés avec resume, filereader, système de fichiers), divers convertisseurs de redimensionneurs d'images utilisant canvas, peuplant des bases de données SQL Web avec base64images et bien plus encore ... Mais dans ces cas, vous devez créer une fonction uniquement pour cela. but ... parfois vous avez besoin d'un blob, de tampons de tableaux, vous pouvez définir des en-têtes, remplacer le type MIME et bien plus encore ...
Mais la question ici est de savoir comment renvoyer une réponse Ajax ... (j'ai ajouté un moyen simple).
Cela signifie AngularJS, jQuery (avec différé), le remplacement natif de XHR (fetch), EmberJS, la sauvegarde de BackboneJS ou toute bibliothèque de noeuds renvoyant des promesses.
Votre code devrait être quelque chose dans le sens de ceci:
function foo() {
var data;
// or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result is always undefined no matter what.
Felix Kling a très bien écrit les réponses aux personnes utilisant jQuery avec des rappels pour AJAX. J'ai une réponse pour XHR native. Cette réponse concerne l’utilisation générique des promesses, que ce soit en amont ou en aval.
Le modèle de concurrence de JavaScript dans le navigateur et sur le serveur avec NodeJS/io.js est asynchrone et réactif .
Chaque fois que vous appelez une méthode qui retourne une promesse, les gestionnaires then
sont toujours exécutés de manière asynchrone - c'est-à-dire après le code situé en dessous d'eux qui ne se trouve pas dans un gestionnaire .then
.
Cela signifie que lorsque vous renvoyez data
, le gestionnaire then
que vous avez défini ne s’est pas encore exécuté. Cela signifie à son tour que la valeur que vous renvoyez n'a pas été définie correctement dans le temps.
Voici une analogie simple pour le problème:
function getFive(){
var data;
setTimeout(function(){ // set a timer for one second in the future
data = 5; // after a second, do this
}, 1000);
return data;
}
document.body.innerHTML = getFive(); // `undefined` here and not 5
La valeur de data
est undefined
puisque la partie data = 5
n'a pas encore été exécutée. Il sera probablement exécuté dans une seconde, mais à ce moment-là, il n’est plus pertinent pour la valeur renvoyée.
Étant donné que l'opération ne s'est pas encore produite (AJAX, appel sur le serveur, IO, timer), vous renvoyez la valeur avant que la demande ait la possibilité d'indiquer à votre code quelle est cette valeur.
Une solution possible à ce problème consiste à coder de manière réactive , en indiquant à votre programme quoi faire lorsque le calcul est terminé. Les promesses le permettent activement en étant de nature temporelle (sensible au temps).
Une promesse est une valeur dans le temps . Les promesses ont un état, elles démarrent en attente sans valeur et peuvent se régler comme suit:
Une promesse ne peut changer d'états qu'une seule fois , après quoi elle restera toujours dans le même état pour toujours. Vous pouvez attacher les gestionnaires then
aux promesses d’extraire leur valeur et de gérer les erreurs. then
les gestionnaires permettent chaîner d'appels. Les promesses sont créées par à l'aide d'API qui les renvoient . Par exemple, le plus moderne AJAX _ remplacement fetch
ou le $.get
de jQuery retournent les promesses.
Lorsque nous appelons .then
sur une promesse et que renvoie quelque chose , nous obtenons une promesse pour la valeur traitée . Si nous retournons une autre promesse, nous obtiendrons des choses incroyables, mais tenons nos chevaux.
Voyons comment nous pouvons résoudre le problème ci-dessus avec des promesses. Tout d'abord, démontrons notre compréhension des états de promesse d'en haut en utilisant le constructeur Promise pour créer une fonction de retard:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
Maintenant, après avoir converti setTimeout pour utiliser les promesses, nous pouvons utiliser then
pour le faire compter:
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
function getFive(){
// we're RETURNING the promise, remember, a promise is a wrapper over our value
return delay(100).then(function(){ // when the promise is ready
return 5; // return the value 5, promises are all about return values
})
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
Fondamentalement, au lieu de renvoyer une valeur , ce que nous ne pouvons pas faire à cause du modèle de simultanéité - nous renvoyons un wrapper pour une valeur que nous pouvons décompresser avec then
. C'est comme une boîte que vous pouvez ouvrir avec then
.
Il en va de même pour votre appel d'API d'origine, vous pouvez:
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // process it inside the `then`
});
}
foo().then(function(response){
// access the value inside the `then`
})
Donc, cela fonctionne aussi bien. Nous avons appris que nous ne pouvons pas renvoyer de valeurs d'appels déjà asynchrones, mais que nous pouvons utiliser des promesses et les chaîner pour effectuer le traitement. Nous savons maintenant comment renvoyer la réponse d'un appel asynchrone.
ES6 introduit générateurs qui sont des fonctions qui peuvent revenir au milieu, puis reprendre le point où elles se trouvaient. Ceci est généralement utile pour les séquences, par exemple:
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
Est une fonction qui renvoie un itérateur sur la séquence 1,2,3,3,3,3,....
qui peut être itérée. Bien que cela soit intéressant en lui-même et ouvre la voie à de nombreuses possibilités, il existe un cas intéressant en particulier.
Si la séquence que nous produisons est une séquence d'actions plutôt que de chiffres, nous pouvons suspendre la fonction à chaque fois qu'une action est générée et l'attendre avant de reprendre la fonction. Ainsi, au lieu d’une suite de nombres, nous avons besoin d’une suite de valeurs futures - c’est-à-dire: promesses.
Cette astuce un peu délicate mais très puissante nous permet d’écrire du code asynchrone de manière synchrone. Il y a plusieurs "coureurs" qui le font pour vous. Écrire une ligne ne représente que quelques lignes de code, mais dépasse le cadre de cette réponse. J'utiliserai le Promise.coroutine
de Bluebird ici, mais il existe d'autres wrappers comme co
ou Q.async
.
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // notice the yield
// code here only executes _after_ the request is done
return data.json(); // data is defined
});
Cette méthode retourne une promesse elle-même, que nous pouvons consommer d'autres coroutines. Par exemple:
var main = coroutine(function*(){
var bar = yield foo(); // wait our earlier coroutine, it returns a promise
// server call done here, code below executes when done
var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
console.log(baz); // runs after both requests done
});
main();
Dans ES7, ceci est davantage standardisé, il y a plusieurs propositions pour le moment, mais vous pouvez toutes les promettre await
. Ceci est juste "sucre" (syntaxe plus agréable) pour la proposition ES6 ci-dessus en ajoutant les mots-clés async
et await
. Voici l'exemple ci-dessus:
async function foo(){
var data = await fetch("/echo/json"); // notice the await
// code here only executes _after_ the request is done
return data.json(); // data is defined
}
Il retourne toujours une promesse quand même :)
Vous utilisez mal Ajax. L'idée n'est pas de lui renvoyer quoi que ce soit, mais de transférer les données à une fonction appelée fonction de rappel, qui gère les données.
C'est:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
Renvoyer quoi que ce soit dans le gestionnaire de soumission ne fera rien. Au lieu de cela, vous devez soit transférer les données, soit en faire ce que vous voulez directement dans la fonction de réussite.
La solution la plus simple consiste à créer une fonction JavaScript et à l'appeler pour le rappel Ajax success
.
function callServerAsync(){
$.ajax({
url: '...',
success: function(response) {
successCallback(response);
}
});
}
function successCallback(responseObj){
// Do something like read the response and show data
alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}
function foo(callback) {
$.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
Pour les personnes qui utilisent AngularJS , pouvez gérer cette situation en utilisant Promises
.
Ici ça dit,
Les promesses peuvent être utilisées pour désanalyser des fonctions asynchrones et permettent de chaîner plusieurs fonctions ensemble.
Vous pouvez trouver une bonne explication ici aussi.
Exemple trouvé dans docs mentionné ci-dessous.
promiseB = promiseA.then(
function onSuccess(result) {
return result + 1;
}
,function onError(err) {
//Handle error
}
);
// promiseB will be resolved immediately after promiseA is resolved
// and its value will be the result of promiseA incremented by 1.
Dans Angular2
avec l'exemple suivant, mais recommandé utiliser Observables
avec Angular2
.
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
Vous pouvez consommer cela de cette façon,
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
Voir le original post ici. Mais TypeScript ne supporte pas Promesses es6 natives , si vous voulez l’utiliser, vous aurez peut-être besoin d’un plugin pour cela.
De plus, voici les promesses spec définir ici.
La plupart des réponses ici donnent des suggestions utiles lorsque vous avez une seule opération asynchrone, mais parfois, cela se produit lorsque vous devez effectuer une opération asynchrone pour chaque entrée dans un tableau ou une autre structure semblable à une liste. La tentation est de faire ceci:
_// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.Push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
_
Exemple:
_// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.Push(result);
});
});
console.log("Results:", results); // E.g., using them, returning them, etc.
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
La raison pour laquelle cela ne fonctionne pas, c'est que les rappels de doSomethingAsync
n'ont pas encore été exécutés au moment où vous essayez d'utiliser les résultats.
Ainsi, si vous avez un tableau (ou une liste quelconque) et que vous souhaitez effectuer des opérations asynchrones pour chaque entrée, vous avez deux options: Effectuez les opérations en parallèle (chevauchement) ou en série (une après l'autre en séquence).
Vous pouvez tous les lancer et noter le nombre de rappels que vous attendez, puis utiliser les résultats lorsque vous avez reçu autant de rappels:
_var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
_
Exemple:
_var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
(On pourrait supprimer expecting
et utiliser simplement _results.length === theArray.length
_, mais cela laisse entrevoir la possibilité que theArray
soit modifié alors que les appels sont en attente ...)
Remarquez comment nous utilisons les variables index
de forEach
pour enregistrer le résultat dans results
dans la même position que celle à laquelle elle se rapporte, même si les résultats arrivent en panne (les appels asynchrones ne se terminant pas nécessairement dans l'ordre dans lequel ils ont été démarrés) ).
Mais que se passe-t-il si vous devez renvoyer les résultats d'une fonction? Comme les autres réponses l'ont souligné, vous ne pouvez pas; votre fonction doit accepter et appeler un rappel (ou renvoyer un Promise ). Voici une version de rappel:
_function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
_
Exemple:
_function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
Ou voici une version renvoyant un Promise
à la place:
_function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
_
Bien sûr, si doSomethingAsync
nous a transmis des erreurs, nous utiliserions reject
pour rejeter la promesse lorsque nous aurions une erreur.)
Exemple:
_function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
(Ou alternativement, vous pouvez créer un wrapper pour doSomethingAsync
qui retourne une promesse, puis faire ce qui suit ci-dessous ...)
Si doSomethingAsync
vous donne un Promise , vous pouvez utiliser Promise.all
:
_function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
_
Si vous savez que doSomethingAsync
ignorera les deuxième et troisième arguments, vous pouvez simplement le transmettre directement à map
(map
appelle son rappel avec trois arguments, mais la plupart des gens n'utilisent que le premier la plupart du temps):
_function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
_
Exemple:
_function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
Notez que _Promise.all
_ résout sa promesse avec un tableau des résultats de toutes les promesses que vous lui donnez quand elles sont toutes résolues, ou rejette sa promesse lorsque le premier des promesses que vous lui faites est rejetée.
Supposons que vous ne vouliez pas que les opérations soient en parallèle? Si vous souhaitez les exécuter les unes après les autres, vous devez attendre la fin de chaque opération avant de commencer la suivante. Voici un exemple de fonction qui fait cela et appelle un rappel avec le résultat:
_function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.Push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
_
(Puisque nous effectuons le travail en série, nous pouvons simplement utiliser results.Push(result)
puisque nous savons que les résultats ne seront pas erronés. Dans ce qui précède, nous aurions pu utiliser _results[index] = result;
_, mais dans certains des exemples suivants, nous n'avons pas d'index à utiliser.)
Exemple:
_function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.Push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
(Ou encore, construisez un wrapper pour doSomethingAsync
qui vous donne une promesse et faites ce qui est en dessous ...)
Si doSomethingAsync
vous donne une promesse, si vous pouvez utiliser la syntaxe ES2017 + (peut-être avec un transpiler tel que Babel ), vous pouvez utiliser une fonction async
avec for-of
et await
:
_async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.Push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
_
Exemple:
_async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.Push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
Si vous ne pouvez pas (encore) utiliser la syntaxe ES2017 +, vous pouvez utiliser une variante du motif modèle "" Réduisez les promesses " (c'est plus complexe que la réduction habituelle de Promise, car nous ne transmettons pas le résultat de un dans l’autre, mais au lieu de rassembler leurs résultats dans un tableau):
_function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.Push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
_
Exemple:
_function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.Push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
... qui est moins encombrant avec ES2015 + fonctions de flèche :
_function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.Push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
_
Exemple:
_function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.Push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
_
_.as-console-wrapper {
max-height: 100% !important;
}
_
Regardez cet exemple:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http) {
var getJoke = function(){
return $http.get('http://api.icndb.com/jokes/random').then(function(res){
return res.data.value;
});
}
getJoke().then(function(res) {
console.log(res.joke);
});
});
Comme vous pouvez le constater, getJoke
est une promesse résolue (il est résolu lors du renvoi de res.data.value
). Vous attendez donc que la demande $ http.get soit terminée, puis console.log (res.joke) est exécuté (en tant que flux asynchrone normal).
Ceci est la commande:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
chemin ES6 (asynchrone - attendre)
(function(){
async function getJoke(){
let response = await fetch('http://api.icndb.com/jokes/random');
let data = await response.json();
return data.value;
}
getJoke().then((joke) => {
console.log(joke);
});
})();
Une autre approche pour renvoyer une valeur d'une fonction asynchrone consiste à transmettre un objet qui stockera le résultat de la fonction asynchrone.
Voici un exemple du même:
var async = require("async");
// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.Push(function(_callback){
// some asynchronous operation
$.ajax({
url: '...',
success: function(response) {
result.response = response;
_callback();
}
});
});
async.parallel(asyncTasks, function(){
// result is available after performing asynchronous operation
console.log(result)
console.log('Done');
});
J'utilise l'objet result
pour stocker la valeur pendant l'opération asynchrone. Cela permet au résultat d'être disponible même après le travail asynchrone.
J'utilise beaucoup cette approche. Je serais intéressé de savoir comment cette approche fonctionne bien lorsque le résultat est renvoyé par le biais de modules consécutifs.
C’est l’un des endroits qui la liaison de données de deux manières qui est utilisé dans de nombreux nouveaux frameworks JavaScript fonctionnera parfaitement pour vous ...
Donc, si vous utilisez Angular, React ou tout autre framework qui le fait liaison de données à deux sens ou concept de magasin, ce problème est simplement résolu pour vous, Donc, dans Word facile, votre résultat est undefined
à la première étape, vous avez donc result = undefined
avant de recevoir les données, puis dès que vous obtenez le résultat, il sera mis à jour et attribué à la nouvelle valeur que la réponse de votre appel Ajax ...
Mais comment faire en pure javascript ou jQuery par exemple comme vous l'avez demandé dans cette question?
Vous pouvez utiliser un rappel, promesse et récemment observable pour le gérer pour vous, par exemple dans les promesses, nous avons une fonction telle que success()
ou then()
qui sera exécuté lorsque vos données seront prêtes, même avec rappel ou s'abonner fonction sur observable.
Par exemple, dans votre cas où vous utilisez jQuery, vous pouvez faire quelque chose comme ceci:
$(document).ready(function(){
function foo() {
$.ajax({url: "api/data", success: function(data){
fooDone(data); //after we have data, we pass it to fooDone
}});
};
function fooDone(data) {
console.log(data); //fooDone has the data and console.log it
};
foo(); //call happens here
});
Pour plus d’informations, étudiez promesses et observables, des méthodes plus récentes pour effectuer cette opération asynchrone.
Alors que les promesses et les rappels fonctionnent très bien dans de nombreuses situations, il est difficile d’exprimer quelque chose du genre:
if (!name) {
name = async1();
}
async2(name);
Vous finiriez par passer par async1
; vérifie si name
n'est pas défini ou non et appelle le rappel en conséquence.
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
Bien que cela soit correct dans les petits exemples, cela devient agaçant lorsque de nombreux cas similaires et le traitement des erreurs sont impliqués.
Fibers
aide à résoudre le problème.
var Fiber = require('fibers')
function async1(container) {
var current = Fiber.current
var result
doSomething(function(name) {
result = name
fiber.run()
})
Fiber.yield()
return result
}
Fiber(function() {
var name
if (!name) {
name = async1()
}
async2(name)
// Make any number of async calls from here
}
Vous pouvez commander le projet ici .
La réponse courte est, vous devez implémenter un rappel comme ceci:
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
L’exemple suivant que j’ai écrit montre comment
Cet exemple de travail est autonome. Il définira un objet requête simple utilisant l’objet window XMLHttpRequest
pour effectuer des appels. Il définira une fonction simple consistant à attendre que de nombreuses promesses soient complétées.
Le contexte. L'exemple interroge le point final API Spotify Web afin de rechercher des objets playlist
pour un ensemble donné de chaînes de requête:
[
"search?type=playlist&q=%22Doom%20metal%22",
"search?type=playlist&q=Adele"
]
Pour chaque élément, une nouvelle promesse déclenche un bloc - ExecutionBlock
, analyse le résultat, planifie un nouvel ensemble de promesses en fonction du tableau de résultats, c'est-à-dire une liste d'objets Spotify user
et exécute le nouveau Appel HTTP dans la ExecutionProfileBlock
de manière asynchrone.
Vous pouvez ensuite voir une structure Promise imbriquée, qui vous permet de générer plusieurs appels HTTP imbriqués complètement asynchrones et de joindre les résultats de chaque sous-ensemble d'appels via Promise.all
.
NOTE Récente Spotify search
Les API nécessiteront la spécification d'un jeton d'accès dans les en-têtes de requête:
-H "Authorization: Bearer {your access token}"
Ainsi, pour exécuter l'exemple suivant, vous devez placer votre jeton d'accès dans les en-têtes de la requête:
var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
log: function(s) {
document.getElementById("console").innerHTML += s + "<br/>"
}
}
// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
call: function(what, response) {
var request;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // Internet Explorer
try {
request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}
// State changes
request.onreadystatechange = function() {
if (request.readyState === 4) { // Done
if (request.status === 200) { // Complete
response(request.responseText)
}
else
response();
}
}
request.open('GET', what, true);
request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
request.send(null);
}
}
//PromiseAll
var promiseAll = function(items, block, done, fail) {
var self = this;
var promises = [],
index = 0;
items.forEach(function(item) {
promises.Push(function(item, i) {
return new Promise(function(resolve, reject) {
if (block) {
block.apply(this, [item, index, resolve, reject]);
}
});
}(item, ++index))
});
Promise.all(promises).then(function AcceptHandler(results) {
if (done) done(results);
}, function ErrorHandler(error) {
if (fail) fail(error);
});
}; //promiseAll
// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
var url = "https://api.spotify.com/v1/"
url += item;
console.log( url )
SimpleRequest.call(url, function(result) {
if (result) {
var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
return item.owner.href;
})
resolve(profileUrls);
}
else {
reject(new Error("call error"));
}
})
}
arr = [
"search?type=playlist&q=%22Doom%20metal%22",
"search?type=playlist&q=Adele"
]
promiseAll(arr, function(item, index, resolve, reject) {
console.log("Making request [" + index + "]")
ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results
console.log("All profiles received " + results.length);
//console.log(JSON.stringify(results[0], null, 2));
///// promiseall again
var ExecutionProfileBlock = function(item, index, resolve, reject) {
SimpleRequest.call(item, function(result) {
if (result) {
var obj = JSON.parse(result);
resolve({
name: obj.display_name,
followers: obj.followers.total,
url: obj.href
});
} //result
})
} //ExecutionProfileBlock
promiseAll(results[0], function(item, index, resolve, reject) {
//console.log("Making request [" + index + "] " + item)
ExecutionProfileBlock(item, index, resolve, reject);
}, function(results) { // aggregated results
console.log("All response received " + results.length);
console.log(JSON.stringify(results, null, 2));
}
, function(error) { // Error
console.log(error);
})
/////
},
function(error) { // Error
console.log(error);
});
<div id="console" />
J'ai longuement discuté de cette solution ici .
C'est assez simple:
Voici une version de travail de votre code:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
wait est supporté par tous les navigateurs actuels et le noeud 8
Vous pouvez utiliser cette bibliothèque personnalisée (écrite avec Promise) pour effectuer un appel distant.
function $http(apiConfig) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open(apiConfig.method, apiConfig.url);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx.
// Your logic here.
resolve(this.response);
}
else {
// Performs the function "reject" when this.status is different than 2xx.
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
}
Exemple d'utilisation simple:
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
Js est un single threaded.
Le navigateur peut être divisé en trois parties:
1) Boucle d'événement
2) API Web
3) file d'attente d'événements
La boucle d'événement est exécutée pour toujours, c'est-à-dire une sorte de boucle infinie. La file d'attente d'événement est l'endroit où toutes vos fonctions sont poussées sur un événement donné (exemple: clic). Cette file d'attente est exécutée une par une et mise en boucle d'événement pour exécuter cette fonction pour l'exécution suivante après exécution, cela signifie que l'exécution d'une fonction ne commence que lorsque celle-ci est exécutée dans la boucle d'événement.
Supposons maintenant que nous ayons placé deux fonctions dans une file d’attente, l’une pour obtenir des données du serveur et une autre utilisant ces données. Nous avons placé la fonction serverRequest () en file d’attente, puis la fonction utiliseData (). La fonction serverRequest se met en boucle d’événement et appelle le serveur car nous ne savons jamais combien de temps il faudra pour obtenir les données du serveur. Ce processus devrait donc prendre du temps. Nous occupons donc notre boucle d’événements en suspendant notre page, c’est là que le Web Les API entrent dans le rôle qu’elles prennent cette fonction de la boucle d’événement et traitent de la création d’une boucle d’événement libre du serveur afin que nous puissions exécuter la fonction suivante de la file d’attente. le gaspillage et l'exécution de la fonction suivante se poursuivent jusqu'à la fin de la file d'attente (cela s'appelle un appel asynchrone, c'est-à-dire que nous pouvons faire autre chose jusqu'à ce que nous obtenions des données).
Supposons que notre fonction serverRequest () contienne une instruction return dans un code. Lorsque nous récupérons des données à partir de l'API Web du serveur, il le place dans la file d'attente à la fin de la file d'attente. Comme il est poussé à la fin de la file d'attente, nous ne pouvons pas utiliser ses données car il n'y a plus de fonction dans notre file d'attente pour utiliser ces données. Il n'est donc pas possible de renvoyer quelque chose à partir de l'appel async.
Ainsi, la solution est un rappel ou promis .
Une image d'une des réponses ici, explique correctement l'utilisation du rappel ... Nous donnons notre fonction (fonction utilisant les données renvoyées du serveur) au serveur appelant de la fonction.
function doAjax(callbackFunc, method, url) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open(method, url);
xmlHttpReq.onreadystatechange = function() {
if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
callbackFunc(xmlHttpReq.responseText);
}
}
xmlHttpReq.send(null);
}
Dans mon code, il est appelé
function loadMyJson(categoryValue){
if(categoryValue==="veg")
doAjax(print,"GET","http://localhost:3004/vegetables");
else if(categoryValue==="fruits")
doAjax(print,"GET","http://localhost:3004/fruits");
else
console.log("Data not found");
}
Lisez ici les nouvelles méthodes dans ECMA (2016/17) pour passer un appel asynchrone (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856
Une autre solution consiste à exécuter du code via un exécuteur séquentiel nsynjs .
nsynjs évaluera toutes les promesses de manière séquentielle et placera le résultat de la promesse dans la propriété data
:
function synchronousCode() {
var getURL = function(url) {
return window.fetch(url).data.text().data;
};
var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
console.log('received bytes:',getURL(url).length);
};
nsynjs.run(synchronousCode,{},function(){
console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
Étape 1. Enroulez la fonction avec le rappel dans l'encapsuleur nsynjs-aware (si elle a une version promise, vous pouvez ignorer cette étape):
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
Étape 2. Mettez la logique synchrone en fonction:
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
Étape 3. Exécutez la fonction de manière synchrone via nsynjs:
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs évaluera tous les opérateurs et expressions, étape par étape, en suspendant l'exécution au cas où le résultat d'une fonction lente n'est pas prêt.
Plus d'exemples ici: https://github.com/amaksr/nsynjs/tree/master/examples
C'est un problème très courant auquel nous sommes confrontés lorsque nous nous débattons avec les "mystères" de JavaScript. Laissez-moi essayer de démystifier ce mystère aujourd'hui.
Commençons par une simple fonction JavaScript:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
Il s'agit d'un simple appel de fonction synchrone (où chaque ligne de code est "terminée avec son travail" avant la suivante dans l'ordre) et le résultat est identique à celui attendu.
Ajoutons maintenant un peu de torsion, en introduisant un peu de retard dans notre fonction, de sorte que toutes les lignes de code ne soient pas "finies" dans l’ordre. Ainsi, il va émuler le comportement asynchrone de la fonction:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
Alors voilà, ce délai vient de casser la fonctionnalité que nous attendions! Mais que s'est-il passé exactement? En fait, c’est plutôt logique si vous examinez le code. la fonction foo()
, lors de l'exécution, ne renvoie rien (la valeur renvoyée est donc undefined
), mais elle active un temporisateur, qui exécute une fonction après 1s pour renvoyer 'wohoo'. Mais comme vous pouvez le constater, la valeur assignée à bar correspond aux éléments renvoyés immédiatement par foo (), et non à tout autre élément ultérieur.
Alors, comment abordons-nous ce problème?
Demandons à notre fonction un PROMISE. Promise, c’est vraiment ce que cela signifie: cela signifie que la fonction vous garantit de fournir le résultat final à venir. voyons donc cela en action pour notre petit problème ci-dessus:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when execution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
Ainsi, le résumé est - pour aborder les fonctions asynchrones telles que les appels basés sur ajax, etc., vous pouvez utiliser une promesse de resolve
la valeur (que vous avez l'intention de renvoyer). Ainsi, en bref, vous résolution valeur au lieu de retourne, dans les fonctions asynchrones.
En plus d'utiliser then/catch
pour travailler avec des promesses, il existe une autre approche. L'idée est de reconnaître une fonction asynchrone puis d'attendre les promesses à résoudre, avant de passer à la ligne de code suivante. Ce n'est toujours que la promises
sous le capot, mais avec une approche syntaxique différente. Pour clarifier les choses, vous pouvez trouver une comparaison ci-dessous:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
throw err;
})
resolve(users);
}
async function fetchUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
throw err;
}
}
ECMAScript 6 a des "générateurs" qui vous permettent de programmer facilement dans un style asynchrone.
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
Pour exécuter le code ci-dessus, procédez comme suit:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
Si vous devez cibler des navigateurs qui ne prennent pas en charge ES6, vous pouvez exécuter le code par le biais de Babel ou de fermeture-compilateur pour générer ECMAScript 5.
Les callback ...args
sont encapsulés dans un tableau et sont déstructurés lorsque vous les lisez afin que le modèle puisse gérer des rappels comportant plusieurs arguments. Par exemple avec node fs :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
var App = App || {};
App = {
getDataFromServer: function(){
var self = this,
deferred = $.Deferred(),
requests = [];
requests.Push($.getJSON('request/ajax/url/1'));
requests.Push($.getJSON('request/ajax/url/2'));
$.when.apply(jQuery, requests).done(function(xhrResponse) {
return deferred.resolve(xhrResponse.result);
});
return deferred;
},
init: function(){
this.getDataFromServer().done(_.bind(function(resp1, resp2) {
// Do the operations which you wanted to do when you
// get a response from Ajax, for example, log response.
}, this));
}
};
App.init();
Réponse courte: Votre méthode foo()
retourne immédiatement, tandis que l'appel $ajax()
s'exécute de manière asynchrone après le retour de la fonction . Le problème est alors de savoir comment ou où stocker les résultats récupérés par l'appel asynchrone une fois qu'il est renvoyé.
Plusieurs solutions ont été données dans ce fil. Le moyen le plus simple consiste peut-être à transmettre un objet à la méthode foo()
et à stocker les résultats dans un membre de cet objet une fois l'appel asynchrone terminé.
function foo(result) {
$.ajax({
url: '...',
success: function(response) {
result.response = response; // Store the async result
}
});
}
var result = { response: null }; // Object to hold the async result
foo(result); // Returns before the async completes
Notez que l'appel à foo()
ne retournera toujours rien d'utile. Cependant, le résultat de l'appel asynchrone sera désormais stocké dans result.response
.
Utilisez une fonction callback()
à l'intérieur du succès foo()
. Essayez de cette façon. C'est simple et facile à comprendre.
var lat = "";
var lon = "";
function callback(data) {
lat = data.lat;
lon = data.lon;
}
function getLoc() {
var url = "http://ip-api.com/json"
$.getJSON(url, function(data) {
callback(data);
});
}
getLoc();
Nous nous trouvons dans un univers qui semble progresser dans une dimension que nous appelons "temps". Nous ne comprenons pas vraiment ce qu'est le temps, mais nous avons développé des abstractions et un vocabulaire qui permettent de raisonner et d'en parler: "passé", "présent", "futur", "avant", "après".
Les systèmes informatiques que nous construisons - de plus en plus - ont le temps comme dimension importante. Certaines choses sont prêtes pour l'avenir. Ensuite, d'autres choses doivent se produire après que ces premières choses se sont finalement produites. C'est la notion de base appelée "asynchronicité". Dans notre monde de plus en plus en réseau, le cas le plus courant d'asynchronicité attend qu'un système distant réponde à une requête.
Prenons un exemple. Vous appelez le laitier et commandez du lait. Quand ça vient, tu veux le mettre dans ton café. Vous ne pouvez pas mettre le lait dans votre café pour l'instant, car il n'est pas encore arrivé. Vous devez attendre qu'il vienne avant de le mettre dans votre café. En d'autres termes, ce qui suit ne fonctionnera pas:
var milk = order_milk();
put_in_coffee(milk);
Parce que JS n'a aucun moyen de savoir qu'il doit attendre pour que order_milk
se termine avant d'exécuter put_in_coffee
. En d'autres termes, il ne sait pas que order_milk
est asynchrone - est quelque chose qui ne produira pas de lait avant un certain temps. JS et d'autres langages déclaratifs exécutent une instruction après l'autre sans attendre.
L’approche classique de ce problème par JS, tirant parti du fait que JS prend en charge les fonctions en tant qu’objets de première classe pouvant être échangés, consiste à transmettre une fonction en tant que paramètre à la demande asynchrone, qu’elle invoquera une fois celle-ci terminée. sa tâche dans le futur. C'est l'approche de "rappel". Cela ressemble à ceci:
order_milk(put_in_coffee);
order_milk
démarre, commande le lait, puis, quand et seulement quand il arrive, il appelle put_in_coffee
.
Le problème avec cette approche de rappel est qu’elle pollue la sémantique normale d’une fonction rapportant son résultat avec return
; à la place, les fonctions ne doivent pas rapporter leurs résultats en appelant un rappel donné en tant que paramètre. En outre, cette approche peut rapidement devenir lourde s’agissant de longues séquences d’événements. Par exemple, supposons que je veuille attendre que le lait soit versé dans le café, puis seulement à ce moment-là, effectue une troisième étape, à savoir boire le café. Je finis par avoir besoin d'écrire quelque chose comme ça:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
où je passe à put_in_coffee
à la fois le lait à mettre dedans, ainsi que l'action (drink_coffee
) à exécuter une fois que le lait a été mis en place. Ce code devient difficile à écrire, à lire, et déboguer.
Dans ce cas, nous pourrions réécrire le code dans la question comme suit:
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
Ce fut la motivation de la notion de "promesse", qui est un type particulier de valeur qui représente un résultat futur ou asynchrone. Cela peut représenter quelque chose qui est déjà arrivé ou qui va arriver dans le futur ou peut ne jamais arriver du tout. Les promesses ont une seule méthode, nommée then
, à laquelle vous transmettez une action à exécuter lorsque le résultat représenté par la promesse a été réalisé.
Dans le cas de notre lait et de notre café, nous concevons order_milk
pour renvoyer une promesse pour le lait qui arrive, puis spécifions put_in_coffee
en tant qu'action then
, comme suit:
order_milk() . then(put_in_coffee)
Un avantage de ceci est que nous pouvons les chaîner ensemble pour créer des séquences d'occurrences futures ("chaînage"):
order_milk() . then(put_in_coffee) . then(drink_coffee)
Appliquons des promesses à votre problème particulier. Nous allons envelopper notre logique de requête dans une fonction, qui retourne une promesse:
function get_data() {
return $.ajax('/foo.json');
}
En fait, tout ce que nous avons fait est d’ajouter un return
à l’appel de $.ajax
. Cela fonctionne parce que $.ajax
de jQuery retourne déjà une sorte de chose qui ressemble à une promesse. (En pratique, sans entrer dans les détails, nous préférerions encapsuler cet appel afin de renvoyer une vraie promesse, ou utiliser une alternative à $.ajax
qui le fait.) Maintenant, si nous voulons charger le fichier et attendre pour qu’il finisse et fasse quelque chose, on peut simplement dire
get_data() . then(do_something)
par exemple,
get_data() .
then(function(data) { console.log(data); });
Lorsque nous utilisons des promesses, nous finissons par passer beaucoup de fonctions dans then
, il est donc souvent utile d'utiliser les fonctions de flèche plus compactes de style ES6:
get_data() .
then(data => console.log(data));
async
Mais il y a toujours quelque chose de vaguement insatisfaisant à l'idée de devoir écrire le code d'une manière si synchrone et d'une manière très différente si asynchrone. Pour synchrone, on écrit
a();
b();
mais si a
est asynchrone, avec des promesses nous devons écrire
a() . then(b);
Ci-dessus, nous avons dit: "JS n'a aucun moyen de savoir qu'il doit attendre pour que le premier appel se termine avant d'exécuter le second". Ce ne serait pas bien s'il y avait était un moyen de le dire à JS? Il s’avère qu’il existe - le mot clé await
, utilisé dans un type spécial de fonction appelé fonction "asynchrone". Cette fonctionnalité fait partie de la prochaine version d'ES, mais est déjà disponible dans les transpilers tels que Babel, avec les bons presets. Cela nous permet d'écrire simplement
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
Dans votre cas, vous pourriez écrire quelque chose comme
async function foo() {
data = await get_data();
console.log(data);
}
Bien sûr, il existe de nombreuses approches telles que la demande synchrone, c'est prometteur, mais de mon expérience, je pense que vous devriez utiliser l'approche de rappel. C'est naturel au comportement asynchrone de Javascript. Ainsi, votre extrait de code peut être réécrit un peu différemment:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
La question était:
Comment renvoyer la réponse d'un appel asynchrone?
qui PEUVENT être interprétés comme:
Comment rendre le code asynchrone synchrone ?
La solution consiste à éviter les rappels et à utiliser une combinaison de promesses et de async/wait .
Je voudrais donner un exemple pour une demande Ajax.
(Bien qu'il puisse être écrit en Javascript, je préfère l'écrire en Python et le compiler en Javascript en utilisant Transcrypt . Ce sera assez clair.)
Permet d’abord d’utiliser JQuery pour que $
soit disponible en tant que S
:
__pragma__ ('alias', 'S', '$')
Définissez une fonction qui retourne une promesse , dans ce cas un appel Ajax:
def read(url: str):
deferred = S.Deferred()
S.ajax({'type': "POST", 'url': url, 'data': { },
'success': lambda d: deferred.resolve(d),
'error': lambda e: deferred.reject(e)
})
return deferred.promise()
Utilisez le code asynchrone comme s'il était synchrone :
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
La réponse la plus parfaite à cette question utilise Promise
.
function ajax(method, url, params) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open(method, url);
xhr.send(params);
});
}
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Il y a un problème avec l'utilisation des promesses!
J'utilisais cette solution pendant un moment jusqu'à ce que je découvre une erreur dans les anciens navigateurs:
Uncaught ReferenceError: Promise is not defined
J'ai donc décidé d'implémenter ma propre classe Promise pour ES3 à ci-dessous compilateurs js si ce n'est pas défini. Ajoutez simplement ce code avant votre code principal, puis utilisez Promise en toute sécurité!
if(typeof Promise === "undefined"){
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.Push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.Push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
}
Plutôt que de vous envoyer du code, deux concepts sont essentiels pour comprendre comment JS gère les rappels et l’asynchronicité. (Est-ce que c'est un mot?)
Il y a trois choses dont vous devez être conscient; La file d'attente; la boucle d'événement et la pile
En termes généraux et simplistes, la boucle d'événement est comme le chef de projet, elle écoute en permanence toutes les fonctions qui veulent s'exécuter et communique entre la file d'attente et la pile.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
Une fois qu'il a reçu un message, il l'ajoute à la file d'attente. La file d'attente est la liste des tâches en attente d'exécution (comme votre demande AJAX.). imaginez ça comme ça:
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
Lorsque l'un de ces messages est sur le point de s'exécuter, il apparaît dans la file d'attente et crée une pile. Cette pile correspond à tout ce que JS doit exécuter pour exécuter l'instruction contenue dans le message. Donc, dans notre exemple, il est dit d'appeler foobarFunc
function foobarFunc (var) {
console.log(anotherFunction(var));
}
Donc, tout ce que foobarFunc doit exécuter (dans notre cas, anotherFunction
) sera propulsé dans la pile. exécuté, puis oublié - la boucle d'événements passera ensuite à l'élément suivant de la file d'attente (ou écoutera les messages)
L'essentiel ici est l'ordre d'exécution. C'est
Lorsque vous appelez AJAX vers un tiers externe ou exécutez un code asynchrone (par exemple, setTimeout), JavaScript dépend d'une réponse avant de pouvoir continuer.
La grande question est quand aura-t-il la réponse? La réponse est que nous ne le savons pas - la boucle d’événement attend donc que le message "Hé me lance". Si JS attendait ce message de manière synchrone, votre application se figerait et serait nul. JS continue donc d'exécuter le prochain élément de la file d'attente en attendant que le message soit ajouté à la file d'attente.
C'est pourquoi avec la fonctionnalité asynchrone, nous utilisons des choses appelées callbacks . C'est un peu comme un promesse littéralement. Comme dans I promesse de retourner quelque chose à un moment donné jQuery utilise des rappels spécifiques appelés deffered.done
deffered.fail
et deffered.always
(entre autres). Vous pouvez les voir tous ici
Donc, ce que vous devez faire est de transmettre une fonction qui devrait être exécutée à un moment donné avec les données qui lui sont transmises.
Parce qu'un rappel n'est pas exécuté immédiatement mais plus tard, il est important de transmettre la référence à la fonction non exécutée. alors
function foo(bla) {
console.log(bla)
}
donc la plupart du temps (mais pas toujours), vous passerez foo
pas foo()
Espérons que cela aura du sens. Lorsque vous rencontrez des problèmes de ce type qui semblent déroutants - je vous recommande vivement de lire la documentation au complet pour au moins bien la comprendre. Cela fera de vous un meilleur développeur.
En utilisant ES2017, vous devriez avoir ceci comme déclaration de fonction
async function foo() {
var response = await $.ajax({url: '...'})
return response;
}
Et l'exécuter comme ça.
(async function() {
try {
var result = await foo()
console.log(result)
} catch (e) {}
})()
Ou la syntaxe Promise
foo().then(response => {
console.log(response)
}).catch(error => {
console.log(error)
})
Voyons d'abord la forêt avant de regarder les arbres.
Il y a beaucoup de réponses informatives avec beaucoup de détails ici, je ne vais pas les répéter. La clé de la programmation en JavaScript est d’avoir d’abord le modèle mental correct de l’exécution générale.
La bonne nouvelle est que si vous comprenez bien ce point, vous ne devrez jamais vous inquiéter des conditions de course. Vous devez avant tout définir la manière dont vous souhaitez organiser votre code comme étant essentiellement la réponse à différents événements discrets, et la manière dont vous souhaitez les relier dans une séquence logique. Vous pouvez utiliser des promesses ou de nouvelles versions asynchrones/wait de niveau supérieur comme outils à cette fin, ou vous pouvez créer vos propres.
Mais vous ne devriez utiliser aucun outil tactique pour résoudre un problème tant que vous n'êtes pas à l'aise avec le domaine du problème actuel. Dessinez une carte de ces dépendances pour savoir ce qui doit être exécuté quand. Tenter une approche ad hoc de tous ces rappels ne vous servira à rien.
Voici un exemple qui fonctionne:
const validateName = async userName => {
const url = "abc/xyz";
try {
const response = await axios.get(url);
return response.data
} catch (err) {
return false;
}
};
validateName("user")
.then(data => console.log(data))
.catch(reason => console.log(reason.message))
Request fonctionne de manière ansynchrone, de sorte que vous ne pouvez pas lire les données de manière synchrone en tant que code typique. Cependant, en utilisant async/await
, vous pouvez créer un code asynchrone qui ressemble/ressemble beaucoup au code de synchronisation. Code dont les données de demande de processus doivent être enveloppées par une fonction asynchrone (load
dans l'extrait ci-dessous) et à l'intérieur de laquelle vous devez ajouter await
keywort avant foo()
(qui utilise également async/await
).
async function foo() {
var url= 'https://jsonplaceholder.typicode.com/todos/1';
var result= await (await fetch(url)).text(); // or .json()
return result;
}
async function load() {
var result = await foo();
console.log(result);
}
load();
utilisation de async/await
avec un transpileur tel que Babel pour le faire fonctionner dans des navigateurs plus anciens. Vous devrez également installer ce paramètre prédéfini et polyfill de Babel à partir de npm: npm i -D babel-preset-env babel-polyfill.
function getData(ajaxurl) {
return $.ajax({
url: ajaxurl,
type: 'GET',
});
};
async test() {
try {
const res = await getData('https://api.icndb.com/jokes/random')
console.log(res)
} catch(err) {
console.log(err);
}
}
test();
ou le rappel .then
n'est qu'un autre moyen d'écrire la même logique.
getData(ajaxurl).then(function(res) {
console.log(res)
}