L'objet Promise représente l'achèvement (ou l'échec) éventuel d'une opération asynchrone et la valeur qui en résulte.
C'est un sujet difficile pour beaucoup de gens, surtout si tu connais la programmation dans un langage complètement synchrone. Si tu te sens dépassé, ou si tu veux en apprendre plus sur la concurrence et le parallélisme, regarde (via go.dev) ou regarde directement sur vimeo et lis les diapositives de la brillante conférence « Concurrency is not parallelism ».
Une Promise possède trois états :
Lorsqu'elle est créée, une promesse est pending. À un moment donné dans le futur, elle peut être résolue ou rejetée. Une fois qu'une promesse a été résolue ou rejetée, elle ne peut plus jamais l'être, et son état ne peut plus changer.
Autrement dit :
Une promesse peut être résolue de différentes manières :
// Creates a promise that is immediately resolved
Promise.resolve(value);
// Creates a promise that is immediately resolved
new Promise((resolve) => {
resolve(value);
});
// Chaining a promise leads to a resolved promise
somePromise.then(() => {
// ...
return value;
});
Dans les exemples ci-dessus, value peut être n'importe quoi, y compris une erreur, undefined, null ou une autre promesse.
En général, on veut résoudre avec une valeur qui n'est pas une erreur.
Une promesse peut être rejetée de différentes manières :
// Creates a promise that is immediately rejected
Promise.reject(reason)
// Creates a promise that is immediately rejected
new Promise((_, reject) {
reject(reason)
})
// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
// ...
throw reason
})
Dans les exemples ci-dessus, reason peut être n'importe quoi, y compris une erreur, undefined ou null.
En général, on veut rejeter avec une erreur.
Une promesse peut être enchaînée avec une action future une fois qu'elle est résolue ou rejetée.
promise.then() est appelée une fois que promise est résoluepromise.catch() est appelée une fois que promise est rejetéepromise.finally() est appelée une fois que promise est résolue ou rejetéeToute promesse est « thenable ».
Cela signifie qu'il existe une fonction then disponible, qui sera exécutée une fois que la promesse d'origine est résolue.
Avec promise.then(onResolved), le callback onResolved reçoit la valeur avec laquelle la promesse d'origine a été résolue.
Cela renvoie toujours une nouvelle promesse « enchaînée ».
Renvoyer une value depuis then résout la promesse « enchaînée ».
Lever une reason dans then rejette la promesse « enchaînée ».
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
const promise2 = promise1.then(function (value) {
console.log(value);
// expected output: "Success!"
return true;
});
Cela affichera "Success!" dans la console après environ 1000 ms.
L'état et la valeur de promise1 seront resolved et "Success!".
L'état et la valeur de promise2 seront resolved et true.
Un second argument est disponible et s'exécute quand la promesse d'origine est rejetée.
Avec promise.then(onResolved, onRejected), le callback onResolved reçoit la valeur avec laquelle la promesse d'origine a été résolue, ou le callback onRejected reçoit la raison pour laquelle la promesse a été rejetée.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return true;
}
function shout(reason) {
console.error(reason.toUpperCase());
return false;
}
const promise2 = promise1.then(log, shout);
"Success!" dans la console après environ 1000 ms.
promise1 seront resolved et "Success!".promise2 seront resolved et true."NOPE!" dans la console.
promise1 seront rejected et Nope!.promise2 seront resolved et false.Il est important de comprendre qu'à cause des règles du cycle de vie, quand elle reject, le resolve qui arrive environ 1000 ms plus tard est silencieusement ignoré, car l'état interne ne peut plus changer une fois qu'elle a été rejetée ou résolue.
Il est important de comprendre que renvoyer une valeur depuis une promesse la résout, et que lever une valeur la rejette.
Quand promise1 est résolue et qu'un onResolved est enchaîné : then(onResolved), alors ce qui suit est une nouvelle promesse qui peut être résolue ou rejetée.
Quand promise1 est rejetée mais qu'un onRejected est enchaîné : then(, onRejected), alors ce qui suit est une nouvelle promesse qui peut être résolue ou rejetée.
Parfois, tu veux capturer les erreurs et ne continuer que lorsque la promesse d'origine reject.
Avec promise.catch(onCatch), le callback onCatch reçoit la raison pour laquelle la promesse d'origine a été rejetée.
Cela renvoie toujours une nouvelle promesse « enchaînée ».
Renvoyer une value depuis catch résout la promesse « enchaînée ».
Lever une reason dans catch rejette la promesse « enchaînée ».
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return 'done';
}
function recover(reason) {
console.error(reason.toUpperCase());
return 42;
}
const promise2 = promise1.catch(recover).then(log);
Dans environ la moitié des cas, cela affichera "Success!" dans la console après environ 1000 ms.
Dans l'autre moitié des cas, cela affichera immédiatement 42 dans la console.
promise1 est résolue, catch est ignoré et on atteint then, qui affiche la valeur dans la console.
promise1 seront resolved et "Success!".promise2 seront resolved et "done" ;promise1 est rejetée, catch est exécuté, ce qui renvoie une valeur, et donc la chaîne est maintenant resolved, et on atteint then, qui affiche la valeur dans la console.
promise1 seront rejected et "Nope!".promise2 seront resolved et "done" ;Parfois, tu veux exécuter du code une fois qu'une promesse s'est stabilisée, qu'elle soit résolue ou rejetée.
Avec promise.finally(onSettled), le callback onSettled ne reçoit rien.
Cela renvoie toujours une nouvelle promesse « enchaînée ».
Renvoyer une value depuis finally copie le statut et la valeur de la promesse d'origine, en ignorant la value.
Lever une reason dans finally rejette la promesse « enchaînée », en écrasant tout statut et valeur ou raison de la promesse d'origine.
Plusieurs de ces méthodes combinées :
const myPromise = new Promise(function (resolve, reject) {
const sampleData = [2, 4, 6, 8];
const randomNumber = Math.round(Math.random() * 5);
if (sampleData[randomNumber]) {
resolve(sampleData[randomNumber]);
} else {
reject('Sampling did not result in a sample');
}
});
const finalPromise = myPromise
.then(function (sampled) {
// If the random number was 0, 1, 2, or 3, this will be
// reached and the number 2, 4, 6, or 8 will be logged.
console.log(`Sampled data: ${sampled}`);
return 'yay';
})
.catch(function (reason) {
// If the random number was 4 or 5, this will be reached and
// reason will be "An error occurred". The entire chain will
// then reject with an Error with the reason as message.
throw new Error(reason);
})
.finally(function () {
// This will always log after either the sampled data is
// logged or the error is raised.
console.log('Promise completed');
});
randomNumber vaut 0-3 :
myPromise sera résolue avec la valeur 2, 4, 6, or 8
finalPromise sera résolue avec la valeur 'yay'
Sampled data: ...Promise completedrandomNumber vaut 4-5 :
myPromise sera rejetée avec la raison 'Sampling did not result in a sample'
finalPromise sera rejetée avec la raison Error('Sampling did not result in a sample')
Promise completed"uncaught rejected promise: Error('Sampling did not result in a sample')" dans la consoleComme montré ci-dessus, reject fonctionne avec une string, et une promesse peut aussi être rejetée avec une Error.
Si l'enchaînement des promesses ou leur utilisation générale n'est pas clair, le tutoriel sur MDN est une bonne ressource à consulter.
Dans cet exercice, tu vas fournir un TranslationService qui propose des services de traduction de base aux membres gratuits, et de la traduction avancée aux membres premium avec des garanties de qualité.
L'API
Tu as trouvé une API de traduction de l'espace qui exécute toute request de traduction en un temps raisonnable.
Tu veux en tirer parti.
Les traducteurs de l'espace sont extrêmement capricieux et détestent la redondance, c'est pourquoi ils fournissent aussi des satellites de stockage API où tu peux fetch les traductions passées sans les déranger.
Récupère une traduction
api.fetch(text) récupère une traduction de text depuis le stockage API et renvoie une promise qui fournit deux valeurs :
translation : la traduction elle-mêmequality : la qualité exprimée sous forme de nombreSi une traduction est introuvable dans le stockage API, l'API lève une erreur NotAvailable.
Les traductions peuvent être ajoutées avec la méthode api.request.
Si 'text' n'est pas traduisible, l'API lève une erreur Untranslatable.
api.fetch('jIyaj');
// => Promise({ resolved: 'I understand' })
Demande une traduction
Certaines traductions existent à coup sûr, mais n'ont pas encore été ajoutées au stockage API. C'est la différence entre NotAvailable (absente du stockage, mais qui peut être demandée) et Untranslatable (qui ne peut pas être traduite).
api.request(text, callback) demande qu'une traduction de text soit effectuée et ajoutée dans le stockage API.
Une fois l'opération terminée, la fonction callback est appelée.
undefined est passé à callback : cela indique que la traduction a réussi et qu'elle est accessible via la méthode api.fetch.error est passée à callback : cela indique que quelque chose s'est mal passé.
L'API spatiale est instable, ce qui signifie qu'elle échoue souvent.
Si cela se produit, tu peux relancer api.request.api.request('majQa’', callback);
// => undefined
//
// later: the passed callback is called with undefined
// because it was successful.
⚠ Attention ! ⚠
L'API opère sa magie en téléportant les différents traducteurs dès qu'une request arrive.
C'est une action très coûteuse, donc il ne faut pas l'appeler quand une traduction est disponible.
Malheureusement, tout le monde ne lit pas le manuel, alors un système a été mis en place pour écarter les clients abusifs.
Si un api.request est appelé pour un text déjà disponible, l'API lève une AbusiveClientError pour cet appel, et pour tous les appels suivants.
Veille à ne jamais demander une traduction si quelque chose a déjà été traduit.
Le service gratuit ne fournit que les traductions actuellement présentes dans le stockage API.
Implémente une méthode free(text) qui fournit aux membres gratuits les traductions qui existent déjà dans le stockage API.
Ignore la qualité et transmets les erreurs levées par l'API.
api.fetch (api.fetch renvoie une promise)service.free('jIyaj');
// => Promise<...> resolves "I understand."
service.free("jIyajbe'");
// => Promise<...> rejects Error("Not yet translated")
Implémente une méthode batch([text, text, ...]) pour les membres gratuits qui traduit un tableau de textes avec le service gratuit, en renvoyant toutes les traductions, ou une seule erreur.
BatchIsEmpty si aucun texte n'est fourniservice.batch(['jIyaj', "majQa'"]);
// => Promise<...> resolves ["I understand.", "Well done!"]
service.batch(['jIyaj', "jIyajbe'"]);
// => Promise<...> rejects new Error("Not yet translated")
service.batch([]);
// => Promise<...> rejects BatchIsEmpty()
Implémente une méthode request(text) pour les utilisateurs premium, qui demande qu'une traduction soit ajoutée au stockage API.
La requête doit réessayer automatiquement en cas d'échec.
Elle ne doit pas effectuer plus de 3 appels pour la même requête (ne froisse pas les traducteurs de l'espace !!!).
api.request ne renvoie pas d'erreur, résous avec undefined
api.request renvoie une erreur, réessaie au maximum deux foisservice.request("jIyajbe'");
// => Promise<...> resolves (with nothing), can now be retrieved using the fetch API
Implémente une méthode premium(text, quality) pour les utilisateurs premium afin de récupérer une traduction.
Si une traduction est NotAvailable, demande la traduction puis récupère-la une fois qu'elle a été ajoutée au stockage API.
La méthode ne doit renvoyer la traduction que si elle atteint un certain seuil de quality.
api.fetch se résout, vérifie la qualité avant de résoudreapi.fetch échoue, demande la traduction à la placeapi.request échoue, transmets l'erreurservice.premium("jIyajbe'", 100);
// => Promise<...> resolves "I don't understand."
service.premium("'arlogh Qoylu'pu'?", 100);
// => Promise<...> rejects QualityThresholdNotMet()
service.premium("'arlogh Qoylu'pu'?", 40);
// => Promise<...> resolves "What time is it?"
N.B.
La traduction correcte de 'arlogh Qoylu'pu'? est How many times has it been heard?.
Inscris-toi sur Exercism pour apprendre et maîtriser JavaScript avec 37 concepts159 exercices, et un vrai mentorat humain, le tout gratuitement.