bonjour, j’ai une requete http.get(url) qui retourne une erreur ETIMEOUT. le probleme c’est que cette erreur fait planter tout mon script… est-ce normal ?
Bonjour,
on est en informatique et en plus avec du réseau, tout peut toujours planter.
C’est… triste mais normal :)\
Autour de ta requête HTTP GET, met un peu de code (avec les bonnes habitudes de ton framework) pour qu’il puisse gérer les cas d’erreur, et donc dire « OK c’est fichu ça n’a pas marché, que fait l’algo, on dit quoi à l’utilisateur ».
Bonjour @grosfaignan,
Je vais considérer que ce n’est pas un troll haha.
Je m’enjoins aux conseils de @abelar_s, et voici une ressource pour correctement gérer les erreurs en Node.js
— Quelles sont les conventions d'erreur en Node.js
Pour ton exemple précis, tu trouveras dans la documentation même de http
la solution :
— HTTP | Node.js v17.0.1 Documentation
La voici :
const http = require('http');
http
.get('http://localhost:3000', { agent }, (res) => {
// ...
})
.on('error', (err) => {
if (err.code === 'ETIMEOUT') {
// handle the error
}
});
Voilà voilà
j’ai fini par trouver, il fallait que je set le timeout dans les options de http.get () mais +1 pour le .on(‹ error ›, …) j’y avais pas penser ^^.
j’en profite pour faire un peut de hors sujet, lorsque j’utilise un callback comment puisje facilement modifier des variable contenue avant la fonction appelante,
exemple
function main(){ let output=[]; http.get(url,(res)=>{ /* je souhaite modifier output ici */ }); }
pas terrible l’affichage du code ? m’y serais-je mal pris ?
Je ne suis pas sûr d’avoir compris la question car le simple fait de faire :
const http = require('http');
const url = 'http://localhost:3000';
function main() {
let output = [];
http.get(url, (res) => {
output = ['nouvelle valeur', 'par exemple'];
console.log(output)
});
}
main();
va afficher
['nouvelle valeur', 'par exemple']
dans la console aussitôt que quelqu’un aura été visiter l’adresse http://localhost:3000
Mais ça me parait trop simple pour que ce soit réellement ce que vous souhaitez faire. Pouvez-vous nous en dire plus ?
en fait oui, ca affiche bien la valeur d’output si je place le console.log ici par contre si j’ecris la fonction comme ceci
const http = require('http');
const url = 'http://localhost:3000';
function main() {
let output = [];
http.get(url, (res) => {
output = [res.value];
});
console.log(output);
}
main();
la valeur d’output n’aura pas été mise a jour en dehors de la fonction de callback pour la simple et bonne raison que la fonction main() execute le console.log avant d’executer le callback d’http.get().
Une solutions que j’ai trouver pour y remedier consiste a mettre un timer après le http.get() qui s’arrette lorsque une condition adéquate est rempli (enfin lorsque c’est possible) mais c’est très loin d’etre pratique et c’est pas très propre…^^.
donc si qqun une autre idée…^^
personne pour une idée ?
Bonjour @grosfaignan,
Le problème que vous avez est celui que je suspectais, mais je n’en étais pas sûr puisque vous indiquiez seulement la zone de mise à jour de la variable. Cela va être l’occasion pour moi de rédiger une réponse exhaustif car c’est LE point cruciale qui est spécifique au JavaScript et que beaucoup de développeur ont dut mal à appréhender quand ils viennent d’autres langages où qu’ils débutent. Je vais utiliser divers raccourcis tout en conservant ce qui est nécessaire à la compréhension de votre problème.
Prenez un thé, ça risque d’être long.
La boucle d’évènement
Quand vous lancez la commande node example.js
, cela lance un processus sur votre machine qui utilise ce que l’on appelle la Boucle d’évènement (event loop
).
Si votre fichier exemple contient ces instructions :
console.log('texte 1'); // ceci est une instruction
console.log('texte 2'); // ceci est une autre instruction
console.log('texte 3'); // ceci est encore une instruction
console.log('texte 4'); // ceci est toujours une instruction
alors cette boucle d’évènement va les exécuter les unes après les autres jusqu’à ce qu’il n’en reste plus une seule.
Une fois toutes les instructions exécutées elle va vous rendre la main, le processus machine va être supprimé et vous pourrez de nouveau réexécuter une commande.
Les instructions asynchrones
En JavaScript, il existe des instructions Synchrones et des instructions Asynchrones. Le fait que les instructions soient synchrones signifient que la totalité de ses instructions seront exécutées à la SUITE dans l’ORDRE pendant un tour de boucle (tick
). Le fait que les instructions soient asynchrone signifie que bien qu’elle s’exécute immédiatement, l’ensemble des sous instructions qu’elles contiennent s’exécutera PLUS TARD potentiellement dans le DESORDRE pendant un autre tour de boucle.
L’exemple précédent c’est donc exécuté en 1 tour de boucle et a produit la sortie suivante avant de rendre la main :
C:\Users\bruno\Desktop>node example.js
texte 1
texte 2
texte 3
texte 4
C:\Users\bruno\Desktop>
Les fonctions asynchrones les plus connues sont les fonctions setTimeout
et setInterval
. Prenons la première pour bien expliquer le concept. Le setTimeout
exécute la fonction en premier paramètre au bout du nombre de milliseconde de son second paramètre.
Aussi la fonction de rappelle pwet
s’exécutera au bout de 1 seconde :
setTimeout(function pwet() {
// execution de code
}, 1000) // 1000 millisecondes
Re effectuons l’exemple précédent comme suit :
console.log('texte 1'); // ceci est une instruction synchrone
setTimeout(function () { // ceci est une instruction asynchrone
console.log('texte 2'); // ceci est une autre instruction synchrone
console.log('texte 3'); // ceci est une autre instruction synchrone
}, 0); // signifie que nous effectuons l'action au bout de 0 secondes
console.log('texte 4'); // ceci est encore une instruction synchrone
et le résultat est
C:\Users\bruno\Desktop>node example.js
texte 1
texte 4
texte 2
texte 3
C:\Users\bruno\Desktop>
Les texte 1
, texte 4
et le setTimeout
ont été pris en charge lors du premier tour de boucle, puis les texte 2
et texte 3
ont été pris en charge lors d’un second tour de boucle puis enfin le programme à rendu la main.
Par exemple ici nous avons 3 tours de boucle qui seront nécessaires :
console.log('texte 1');
setTimeout(function () {
console.log('texte 2');
}, 1000);
setTimeout(function () {
console.log('texte 3');
}, 500);
console.log('texte 4');
La sortie sera donc :
C:\Users\bruno\Desktop>node example.js
texte 1
texte 4
et le programme NE RENDRA PAS la main… puis au bout de 0,5 seconde nous verrons une nouvelle ligne en plus comme par exemple
C:\Users\bruno\Desktop>node example.js
texte 1
texte 4
texte 3
et une dernière au bout de 1 seconde (0,5 seconde encore plus tard donc)
C:\Users\bruno\Desktop>node example.js
texte 1
texte 4
texte 3
texte 2
C:\Users\bruno\Desktop>
et, le programme rendra la main, le processus se terminera.
Définition et appel de fonction
Il est important de comprendre que c’est bien au moment de l’exécution de la fonction de rappel qu’on décide si les instructions qu’elle contient vont être traitée de manière synchrone ou asynchrone et non pas lors de la « définition ».
Observons cela :
// Cette fonction va être appelée de manière identique...
function test(i) {
return function () {
console.log('texte ' + i);
}
}
// Par une fonction qui contient que des instructions synchrones
function setSynchrone(callback) {
callback() // synchrone
}
// Et par une fonction qui contient au moins une instruction asynchrone
function setAsynchrone(callback) {
setTimeout(callback, 0) // asynchrone
}
// Dans tous les cas, c'est bien la même fonction `test` que nous appelons
setSynchrone(test(1)) // synchrone, tour de boucle 1
setAsynchrone(test(2)) // asynchrone, tour de boucle 2 ou 3
setAsynchrone(test(3)) // asynchrone, tour de boucle 2 ou 3
setSynchrone(test(4)) // synchrone, tour de boucle 1
Et le résultat
C:\Users\bruno\Desktop>node example.js
texte 1
texte 4
texte 2
texte 3
C:\Users\bruno\Desktop>
ou
C:\Users\bruno\Desktop>node example.js
texte 1
texte 4
texte 3
texte 2
C:\Users\bruno\Desktop>
Boucle d’évènement continue
Jusqu’à maintenant, notre programme nous rend la main, mais si nous utilisons des fonctions asynchrones dont la fonction est d’écouter des évènements ou à intervalle régulier, alors la boucle d’évènement tourne à l’infini attendant en permanence que la fonction de rappel renvoi quelque chose. C’est le cas de setInterval
qui exécute la fonction en paramètre 1 tous les paquets de X millisecondes données en paramètre 2.
console.log('texte 1');
setInterval(function () {
console.log('texte 2');
}, 1000);
console.log('texte 3');
et le résultat sera
C:\Users\bruno\Desktop>node example.js
texte 1
texte 3
puis 1 seconde après
C:\Users\bruno\Desktop>node example.js
texte 1
texte 3
texte 2
puis encore 1 seconde après
C:\Users\bruno\Desktop>node example.js
texte 1
texte 3
texte 2
texte 2
et ainsi de suite
C:\Users\bruno\Desktop>node example.js
texte 1
texte 3
texte 2
texte 2
texte 2
C’est exactement ce que fait votre programme ! Il ne vous rend pas la main car la fonction de rappel que vous passez en second argument de http.get
, c.-à-d.
(res) => {
output = [res.value];
}
est appelée de manière asynchrone.
Et non pas toutes les X millisecondes, mais À CHAQUE FOIS que quelqu’un visite url
avec son navigateur.
Quel est votre problème ?
Regardons ça ensemble. En fait vous espérez que votre code fonctionne comme-ci toutes les instructions était synchrones. Vous remarquerez d’ailleurs qu’avec un timer, la valeur est toujours bonne au bout de 2 secondes. Si le programme fonctionnait comme vous l’espériez, il vous rendrait la main. Voyons une simulation.
const http = {
get: function (url, callback) {
callback({
value: ['tableau', 'renvoyé', 'par', 'le', 'serveur']
})
}
}
const url = 'http://localhost:3000';
function main() {
let output = [];
console.log(1, output);
http.get(url, (res) => {
console.log(2, output);
output = res.value;
console.log(3, output);
});
console.log(4, output);
setTimeout(function () {
console.log('check', output);
}, 2000)
}
main();
Ce qui retourne exactement ce que vous souhaitez
C:\Users\bruno\Desktop>node example.js
1 []
2 []
3 [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
4 [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
puis 2000 millisecondes plus tard
C:\Users\bruno\Desktop>node example.js
1 []
2 []
3 [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
4 [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
check [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
C:\Users\bruno\Desktop>
Puis rend la main.
Cependant, comme vous l’aurez compris, en réalité, http.get
est asynchrone et utilise la boucle indéfiniment. Ce qui fait que le code fonctionne plutôt comme celui de cette simulation :
const http = {
get: function (url, callback) {
function urlListener(i) {
return function () {
// On simule une visite au bout d'un petit nombre d'écoute
// en vrai ça se traduit par un utilisateur qui visite le site
if (i === 50) {
callback({
value: ['tableau', 'renvoyé', 'par', 'le', 'serveur']
})
}
setTimeout(urlListener(i + 1), 1)
}
}
urlListener(0)()
}
}
const url = 'http://localhost:3000';
function main() {
let output = [];
console.log(1, output); // synchrone, pas encore visité
http.get(url, (res) => { // asynchrone et visité au bout de quelque tours
console.log(2, output); // synchrone et visité
output = res.value; // synchrone et visité
console.log(3, output); // synchrone et visité
});
console.log(4, output); // synchrone, pas encore visité
setTimeout(function () {
console.log('check', output);
}, 2000) // au bout de deux seconde le site a été visité, le résultat est présent
}
main();
et on met bien en évidence votre problème. Immédiatement le code synchrone est exécuté
C:\Users\bruno\Desktop>node example.js
1 []
4 []
Et donc votre objet N’est PAS rempli puisque c’est lors d’un tour de boucle qui n’interviendra qu’après que votre utilisateur visite votre page web qu’il sera rempli
C:\Users\bruno\Desktop>node example.js
1 []
4 []
2 []
3 [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
Et enfin, le timer qu’on avait mis en place au bout de deux secondes
C:\Users\bruno\Desktop>node example.js
1 []
4 []
2 []
3 [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
check [ 'tableau', 'renvoyé', 'par', 'le', 'serveur' ]
Bien sur le programme ne vous rendra pas la main puisqu’il va « écouter » tant que vous ne l’aurez pas tué avec Ctrl + C
.
Quelles solutions ?
Et bien en fait pour le moment, vous n’avez pas besoin de solution
dans le sens ou effectivement votre variable output
est bien rempli comme vous le souhaitiez ! C’est juste que vous ne le verrez juste pas dans un console.log
a l’endroit que vous aviez prévu mais que vous le verrez bien dans un console.log
que vous exécuteriez dans la zone synchrone de la fonction de rappel de http.get
ou dans un timer exécuté après la visite d’un utilisateur.
Ça signifie donc juste que vous devez faire exécuter vos fonctions devant manipuler output
après que cette variable soit dans l’état que vous souhaitiez pour en faire quelque chose :
Par exemple ici :
http.get('/url-a', (res) => {
output = res.value;
console.log(output);
});
http.get('/url-b', (res) => {
if (output.length > 0) {
console.log(output);
} else {
console.log('/url-b not yet visited')
}
});
vous n’afficherez le tableau que si quelqu’un a déjà visité /url-a
, sinon vous direz qu’il n’est pas encore disponible à quelqu’un qui viendrait visité /url-b
.
Donc la solution va dépendre de ce que vous souhaitez faire de cette valeur. Gardez juste à l’esprit que tout ce qui n’est pas dans http.get
va s’exécuter de manière Synchrone au moment ou vous lancez votre serveur. Une fois le serveur lancé, les seules zones encore « vivante » sont celle qui auront été lancée en boucle de manière asynchrone.
Bienvenue dans la philosophie Asynchrone de Node.js ou tout n’est que fonction qui attend qu’une autre fonction ai fini quelque chose pour en faire quelque chose et ainsi de suite !