Inscrivez-vous gratuitement pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter
Vous n'êtes pas encore inscrit sur Developpez.com ?
{"Title": "Translation example",
"Intro": "Hello I am et, I am 500 years old.",
"Startpage": {"TranslationSections": "Hello World",
"TranslationsSubSections" :{"TitleOne" : "Hello All",
"TitleTwo" : "Hello You",
"TitleTree" : "Hello Me"}},
"Aboutpage": {"TranslationSections": "We are letsboot"}}
quand j'essaie de stoker les valeurs de ma promesses dans un tableau
Bon, j’essaye de comprendre la logique de ton code mais je pense qu’il manque des éléments. Je vais essayer de t’aider quand même.
Je reconnais que je ne t’ai pas donné beaucoup d’indications la dernière fois. Promise.all attend en paramètre un tableau d’objets Promise, et renvoie une nouvelle promesse qui est résolue si et seulement si toutes les promesses du tableau sont résolues. Mais voyons ce qui se passe dans ton code :
Tu passes un tableau à Promise.all, mais je ne sais pas ce que renvoie getValues. Cette fonction renvoie-t-elle une promesse ? Un tableau de promesses ? Je pense qu’il y a un niveau de tableau en trop.
Soit getValues renvoie une seule promesse, et dans ce cas tu n’as pas besoin de Promise.all ;
soit elle renvoie déjà un tableau, et dans ce cas tu n’as pas besoin des crochets [ ... ].
Note : si le tableau passé à Promise.all contient autre chose que des promesses, ça va marcher quand même (en tout cas sous Firefox, je viens de tester), et ça va considérer que tu as passé des promesses déjà résolues. Mais c’est un peu « par accident » et je ne crois pas que ce soit une bonne idée de se reposer sur ce comportement.
Je ne sais pas si ça va t’aider, mais j’ai lu récemment un article de blog qui proposait de transformer la fonction setTimeout en quelque chose qui retourne une promesse. Par exemple :
Dans le cas présent, la console affichera un tableau contenant quatre fois undefined, qui s’explique par le fait qu’on avait quatre promesses dans le tableau de départ, et que ces promesses ne se résolvent en aucune valeur.
Tu trouveras peut-être que cet exemple n’était pas très parlant. Prenons un autre cas simple : des promesses qui se résolvent instantanément (mais attention, une promesse se résoud toujours de manière asynchrone !)
Encore une fois, je te laisse l’exercice avec la réponse en spoiler. On veut quelque chose qui se comporte comme ceci :
let promise42 = newPromise((resolve, reject) => {resolve(42); });
On peut corser un tout petit peu les choses en souhaitant quelque chose de plus dynamique : une fonction qui reçoit un paramètre et qui retourne une promesse qui se résoud en ce paramètre.
Un peu plus haut, j’ai rapidement attiré ton attention sur le fait que les promesses se résolvent toujours de façon asynchrone. C’est une manière savante de dire « pas dans l’ordre ». Tu auras sans doute remarqué ce genre de phénomène :
Une autre façon de faire : si tu utilises un return (ou la forme « expression » des fonctions flèches) à l’intérieur d’une fonction .then, tu peux chaîner les .then :
J’ai l’impression que tu as deux variables dans ta fonction mytest qui remplissent le même rôle : valuesArray et translatedArray. À mon avis tu devrais retirer une des deux.
Pour le reste, je pense que tu n’es pas loin d’avoir quelque chose qui marche. Et je crois qu’en fin de compte, tu n’auras pas besoin de Promise.all. En fait, Promise.all pourrait même te poser problème car, vu que tu fais un certain nombre de requêtes réseau, il y a une certaine probabilité qu’au moins une d’entre elles échoue, et donc que Promise.all renvoie un résultat négatif. Malheureusement, il n’existe pas de fonction native Promise.most qui permettrait de ne pas tenir compte des échecs… Cela dit, rien ne nous empêche de la coder nous-mêmes.
Attention aux noms que tu donnes à tes arguments de fonctions. Il y a un manque de cohérence entre cette ligne :
Également, la variable body dans getTranslation n’est pas utilisée à la fin. J’imagine que c’est un oubli.
Un détail à propos de ta fonction récursive : à l’intérieur tu appelles un forEach. Je ne sais pas quelle profondeur peut atteindre ta structure de données, mais j’ai déjà vu certains scripts échouer à cause de la pile d’appels qui était surchargée. Dans ton cas tu peux limiter ce risque en remplaçant la fonction forEach par une boucle for..of combinée à Object.entries :
Il viendrait en dernier si tu n’avais pas demandé un setTimeout d’une seconde… Une seconde c’est énorme en comparaison du temps d’exécution d’une fonction JavaScript moyenne, même récursive. Tu peux t’en rendre compte en plaçant des performance.now() avant et après l’appel à mytest :
functionsendToGoogleTranslate(array, lang){if(array && array.size > 0){let t0 = performance.now();
let myloop = mytest(array, lang);
let t1 = performance.now();
console.log(t1 - t0); // affiche des millisecondesPromise.all(myloop).then(function(promises){
console.log("Arriver en dernier");
});
}}
Je suis à peu près certain que ça t’affichera un nombre qui est largement inférieur à 1000.
Je pense qu’il te manque la compréhension de ce qui est synchrone et ce qui ne l’est pas.
Une résolution de promesse est asynchrone. Une fonction appelée avec setTimeout, setInterval ou requestAnimationFrame est asynchrone. Un gestionnaire d’évènement, ce qui inclut les évènements ajax mais aussi les évènements souris/tactile et clavier, est asynchrone. Un appel récursif, une boucle for ou while, une création d’objet, tout ça est synchrone. Généralement, tout le reste est synchrone, mais il y a peut-être des cas que j’ai oubliés.
Sous les navigateurs actuels, (si on oublie la récente technologie des workers) JavaScript n’a qu’un seul fil (thread) d’exécution. Le code asynchrone est placé en attente, et est exécuté immédiatement dès que le thread est libre.
Imagine une fonction simple contenant uniquement des instructions synchrones :
functionf(){let x = 5;
x = x * 2;
x = x + 1;
Promise.resolve("coucou")
.then(functiong(value){ console.log(value); });
console.log(x);
}
La fonction que nous passons à .then, que j’ai nommée g pour plus de lisibilité, est placée en file d’attente jusqu’à ce que le thread soit libéré. En l’occurence, quand la fonction f() se termine.
On peut voir que, en quelques sortes, un nouveau bloc a été « sorti » du bloc principal. Et on voit bien que la valeur de x (11) va s’afficher avant le « coucou ».
Quand tu utilises setTimeout, un délai est rajouté artificiellement pour retarder l’exécution de la fonction.
Tout ça pour dire que je ne comprends pas pourquoi tu as voulu mettre un setTimeout dans ta promesse. Est-ce que je t’ai induit en erreur avec mes explications précédentes ?
La FAQ JavaScript – Les cours JavaScript Touche F12 = la console → l’outil indispensable pour développer en JavaScript !
il me manque encore des éléments pour avoir une compréhension globale de ton code. Par exemple, je ne sais pas ce que fait getTranslation, et je ne sais pas à quel moment sendToGoogleTranslate est appelée.
Attention également à ne pas confondre array et map, les deux existent en JS et ont un fonctionnement différent.
La FAQ JavaScript – Les cours JavaScript Touche F12 = la console → l’outil indispensable pour développer en JavaScript !
Un truc à bien comprendre c’est que chaque appel à .then renvoie une nouvelle promesse, et cette promesse prend pour valeur ce qui est retourné par la fonction passée en paramètre. En clair :
let p1 = newPromise( ... );
let p2 = p1.then((value) => {return42; });
let p3 = p2.then( ... );
p1, p2 et p3 sont trois promesses différentes. p2 se résoud avec la valeur 42 car c’est ce qui est renvoyé par la fonction passée à l’appel .then qui la crée. J’espère que ce n’est pas trop confus
Ce qui est retourné ici est une promesse, pas celle créée par newPromise mais celle qui est créée par .then. On vient de le voir, ce sont deux promesses différentes. Cette dernière promesse se résoud avec la valeur renvoyée par console.log. Or, console.log renvoie toujours undefined… Tu obtiens donc, au final, une promesse qui se résoud avec undefined pour valeur. Ajoute un return value pour joindre les deux bouts.
{"Title": "Translation example",
"Intro": "Hello I am et, I am 500 years old.",
"Startpage": {"TranslationSections": "Hello World",
"TranslationsSubSections" :{"TitleOne" : "Hello All",
"TitleTwo" : "Hello You",
"TitleTree" : "Hello Me"}},
"Aboutpage": {"TranslationSections": "We are letsboot"}}
Techniquement, ce n’est pas un tableau. Ce n’est pas non plus une Map – ou alors c’en est une mais tu ne l’as pas écrite de la même façon que les autres maps que tu montres.
Dans tous les cas, si ce n’était ni un tableau ni une map, il n’aurait pas de méthode forEach et tu ne pourrais pas le passer tel quel à ta fonction récursive. Ce qui me fait me poser la question : qu’est-ce que tu passes en réalité à mytest ? Quelle est la valeur initiale de ce paramètre array ?
Note que tester if(value instanceofObject) ne garantit pas que l’objet en question aura la méthode forEach. Actuellement, cette partie de ton code marche par accident. Tu rendras ce test plus robuste en remplaçant Object par Array ou Map, selon quel est le vrai type de ton argument.
Sinon, de manière plus pragmatique, tu pourrais simplement tester la présence de la méthode forEach :
TRANSLATED WORD (fr)['exemple de traduction',
'bonjour je suis et, j\'ai 500 ans.',
'bonjour le monde',
'bonjour à tous',
'salut toi',
'bonjour moi',
'nous sommes letsboot']
Je dois préciser que je n’avais pas compris jusqu’à maintenant que tu étais côté serveur. Mais ça ne change pas grand chose.
Juste pour être bien clair, je voudrais insister sur la différence entre Array et Map. Les deux ont une méthode forEach, mais le nombre d’éléments se mesure avec length pour les arrays, et size pour les maps. Un array n’a pas de méthode set et ne devrait être utilisé qu’avec des index numériques. Les maps ont été ajoutées au langage, entre autres, pour pallier un manque de fonctionnalité qui faisait que les développeurs utilisaient des chaînes comme index d’arrays et avaient des problèmes à cause de ça.
Note que j’ai fait exprès de ne pas utiliser le mot « tableau » dans le paragraphe précédent. Pour moi, tableau signifie Array, mais j’ai l’impression que tu l’utilises de manière interchangeable pour Array et Map.
Tu admettras que l’écriture array.size prête à confusion. Malgré ça je suis à peu près convaincu que tu es capable de bien faire la distinction, et que c’est juste le nom de la variable qui est inapproprié.
Ok ok, on discute terminologie et on ne fait pas avancer le schmilblick.
Il y a un problème assez subtil avec Promise.all dans ton code actuel, et je vais essayer de te l’expliquer à travers ce petit exemple.
let resolveP;
let p = newPromise((resolve, reject) => {
resolveP = resolve;
});
Promise.all([Promise.resolve(42), p, "un truc au pif"])
.then((values) => {
values.forEach((v) => { console.log(v); });
return values;
});
J’ai préparé un Promise.all qui reçoit un tableau de trois éléments. Le premier élément est une promesse déjà résolue, elle a 42 pour valeur. Le troisième élément est une valeur primitive, une chaîne. Il est important de noter que ce n’est pas une promesse, mais la fonction Promise.all est tolérante et la considère comme une promesse résolue.
Le deuxième élément, p, est une promesse, que j’ai créée juste avant, qui n’est pas encore résolue. Ce sera le déclencheur. Pour la résoudre au moment voulu, j’ai fait sortir la fonction resolve de sa portée locale en l’assignant à une variable extérieure, resolveP.
Au moment où on va appeler resolveP, on va donc résoudre p, et du même coup le Promise.all dont tous les autres éléments sont déjà résolus.
Les choses se font dans l’ordre dans lequel on a passé les choses à Promise.all ;
La fonction de .then reçoit des valeurs directes et pas des promesses.
La chose moins évidente, mais celle qui est importante dans notre situation, c’est que Promise.all accepte des valeurs qui ne sont pas des promesses, et les considère comme des promesses résolues. Ça implique que si tu lui passes uniquement des non-promesses, la promesse résultante va se résoudre instantanément.
Certes, ta fonction mytest renvoie un tableau (array), mais si j’en crois ce que tu me montres :
TRANSLATED WORD (fr)['exemple de traduction',
'bonjour je suis et, j\'ai 500 ans.',
'bonjour le monde',
'bonjour à tous',
'salut toi',
'bonjour moi',
'nous sommes letsboot']
ce n’est pas un tableau de promesses, c’est simplement un tableau de chaînes. mytest utilise des promesses en interne, mais le reste de ton script ne le sait pas. Et comme Promise.all se résoud immédiatement, tes données ne sont pas prêtes quand la fonction du .then est appelée.
Fais bien attention à la valeur de retour de ta fonction récursive mytest.
Dans l’appel initial let myloop = mytest(array, lang);, tu l’utilises avec l’intention de récupérer un tableau de promesses. Dans l’appel récursif translatedArray.set(key, mytest(value, lang));, ton intention est de récupérer les données directement.
Il me semble que ces deux usages sont en conflit.
Classiquement, quand on fait de la programmation récursive, l’information peut circuler dans deux sens : vers le bas (par les paramètres) ou vers le haut (par valeur de retour). On a aussi la troisième voie : utiliser une variable extérieure. Il me semble que c’est ce que tu faisais dans une version précédente de ton code.
Voici mon conseil :
ne change rien aux paramètres,
utilise une variable extérieure (je propose translationsMap) pour stocker les données,
sers-toi de la valeur de retour pour faire remonter les promesses.
N’oublie pas de gérer ces transferts d’informations dans toutes les branches if/else de la fonction.
Quand toutes les promesses sont résolues, si tu les as toutes fait remonter correctement, Promise.all se résoud alors, et tu sais qu’à ce moment tu peux utiliser les données présentes dans translationsMap.
Le récursif c’est toujours un peu magique, mais relie bien les tuyaux et ça devrait bien se passer
Après ça on essayera de coder une variante de Promise.all qui tolère les échecs de requêtes.
La FAQ JavaScript – Les cours JavaScript Touche F12 = la console → l’outil indispensable pour développer en JavaScript !
Eh bien, je ne vois pas de bug flagrant et les arguments et retours ont l’air de correspondre aux types attendus. Du coup, je ne vois pas trop pourquoi tu attends mon avis. Il reste à améliorer ce test typeof ... === 'object', mais ce n’est pas prioritaire.
Tu dis que tu as une « solution qui marche », ça veut dire quoi exactement ? Tu arrives à récupérer la map avec toutes les traductions ?
La FAQ JavaScript – Les cours JavaScript Touche F12 = la console → l’outil indispensable pour développer en JavaScript !
Partager