Eric Elliott

Suivre

Jan 23, 2017 · 11 min en lecture

Photo par Kabun (CC BY NC SA 2.,0)

« maîtriser L’entretien JavaScript” est une série de messages conçus pour préparer les candidats aux questions courantes qu’ils sont susceptibles de rencontrer lorsqu’ils postulent pour un poste JavaScript de niveau intermédiaire à Supérieur. Ce sont des questions que j’utilise fréquemment dans de vraies interviews.

Une promesse est un objet qui peut produire une seule valeur dans le futur: soit une valeur résolue, soit une raison pour laquelle elle n’est pas résolue (par exemple, une erreur réseau s’est produite)., Une promesse peut être dans l’un des 3 états possibles: remplie, rejetée ou en attente. Les utilisateurs de Promise peuvent joindre des rappels pour gérer la valeur remplie ou la raison du rejet.

Les promesses sont impatientes, ce qui signifie qu’une promesse commencera à faire la tâche que vous lui confiez dès que le constructeur de la promesse sera invoqué. Si vous avez besoin de paresseux, consultez les observables ou les tâches.

An Incomplete History of Promises

Les premières implémentations de promises and futures (une idée similaire / liée) ont commencé à apparaître dans des langages tels que MultiLisp et Prolog Concurrent dès les années 1980., L’utilisation du mot « promesse” a été inventée par Barbara Liskov et Liuba Shrira en 1988.

la première fois que j’ai entendu parler de promises en JavaScript, Node était tout nouveau et la communauté discutait de la meilleure façon de gérer le comportement asynchrone. La communauté a expérimenté avec des promesses pendant un certain temps, mais a finalement opté pour les rappels Node-standard error-first.

à peu près au même moment, Dojo a ajouté des promesses via L’API différée. L’intérêt et l’activité croissants ont finalement conduit à la nouvelle spécification Promises / a conçue pour rendre diverses promesses plus interopérables.,

les comportements asynchrones de jQuery ont été refactorisés autour des promesses. le support de promesse de jQuery avait des similitudes remarquables avec différé de Dojo, et il est rapidement devenu l’implémentation de promesse la plus couramment utilisée en JavaScript en raison de l’immense popularité de jQuery — pendant un certain temps. Cependant, il ne prenait pas en charge le comportement de chaînage à deux canaux (rempli/rejeté) & gestion des exceptions sur laquelle les gens comptaient pour créer des outils au-dessus des promesses.,

malgré ces faiblesses, jQuery a officiellement fait des promesses JavaScript grand public, et de meilleures bibliothèques de promesses autonomes comme Q, When et Bluebird sont devenues très populaires. les incompatibilités d’implémentation de jQuery ont motivé des clarifications importantes dans la spécification promise, qui a été réécrite et rebaptisée en tant que spécification Promises/A+.,

ES6 a apporté unPromise global, et certaines API très importantes ont été construites sur le support de la nouvelle promesse standard: notamment la spécification WHATWG Fetch et la norme Async Functions (une ébauche de stage 3 au moment de la rédaction de cet article).

Les promesses décrites ici sont celles qui sont compatibles avec la spécification Promises/a+, en mettant l’accent sur la norme ECMAScriptPromise implémentation.

Comment fonctionnent les promesses

Une promesse est un objet qui peut être retourné de manière synchrone à partir d’une fonction asynchrone., Ce sera dans l’un des 3 états possibles:

Une promesse est réglée si elle n’est pas en attente (elle a été résolue ou rejetée). Parfois, les gens utilisent résolu et réglé pour signifier la même chose: non en attente.

Une fois installée, une promesse ne peut pas être réinstallée. Appel de resolve() ou reject() nouveau n’aura aucun effet. L’immuabilité d’une promesse établie est une caractéristique importante.

Les promesses JavaScript natives n’exposent pas les états de promesse. Au lieu de cela, vous êtes censé traiter la promesse comme une boîte noire., Seule la fonction responsable de la création de la promesse aura connaissance du statut de la promesse, ou l’accès à résoudre ou rejeter.

Voici une fonction qui retourne une promesse qui permettra de résoudre bout d’un certain temps:

attendez — promesse exemple sur CodePen

Notre wait(3000) appel d’attendre 3000ms (3 secondes), puis connectez-vous 'Hello!'., Toutes les promesses compatibles avec les spécifications définissent une méthode.then() que vous utilisez pour passer des gestionnaires qui peuvent prendre la valeur résolue ou rejetée.

le constructeur de la promesse ES6 prend une fonction. Cette fonction prend deux paramètres, resolve() et reject(). Dans l’exemple ci-dessus, nous utilisons uniquement resolve(), j’ai donc laissé reject() hors de la liste des paramètres. Ensuite, nous appelons setTimeout() pour créer le délai d’appel et l’appel resolve() quand c’est fini.,

Vous pouvez resolve() ou reject() avec des valeurs, qui sera passé à la fonction de rappel des fonctions attachées avec des .then().

Quand j’ reject() avec une valeur, je passe toujours un Error objet. Généralement, je veux deux états de résolution possibles: le chemin heureux normal, ou une exception — Tout ce qui empêche le chemin heureux normal de se produire. Passer un objetError rend cela explicite.,

règles de promesse importantes

Une norme pour les promesses a été définie par la communauté de spécifications Promises / A+. Il existe de nombreuses implémentations conformes à la norme, y compris la norme JavaScript ECMAScript promises.

Les promesses suivant la spécification doivent suivre un ensemble de règles spécifiques:

  • Une promesse ou « thenable” est un objet qui fournit une méthode.then() conforme aux normes.
  • Une promesse en attente peut passer à un état rempli ou rejeté.,
  • Une promesse remplie ou rejetée est réglée, et ne doit pas passer dans un autre État.
  • Une fois qu’une promesse est réglée, elle doit avoir une valeur (qui peut êtreundefined). Cette valeur ne doit pas changer.

Le changement dans ce contexte fait référence à la comparaison d’identité (===). Un objet peut être utilisé comme valeur remplie et les propriétés de l’objet peuvent muter.,

Toutes les promesses doivent fournir un .then() méthode avec la signature suivante:

promise.then(
onFulfilled?: Function,
onRejected?: Function
) => Promise

Le .then() méthode doit se conformer à ces règles:

  • Deux onFulfilled() et onRejected() sont en option.
  • Si les arguments fournis ne sont pas des fonctions, ils doivent être ignorés.
  • onFulfilled() sera appelé une fois la promesse remplie, avec la valeur de la promesse comme premier argument.,
  • onRejected() sera appelé après le rejet de la promesse, avec la raison du rejet comme premier argument. La raison peut être n’importe quelle valeur JavaScript valide, mais parce que les rejets sont essentiellement synonymes d’exceptions, je recommande d’utiliser des objets D’erreur.
  • Ni onFulfilled() ni onRejected() peut être appelé plus d’une fois.
  • .then() peut être appelée plusieurs fois sur la même promesse. En d’autres termes, une promesse peut être utilisée pour agréger les rappels.,
  • .then() doit retourner une nouvelle promesse, promise2.
  • Si onFulfilled() ou onRejected() retourner une valeur x et x c’est une promesse, promise2 verrouillage, avec (à supposer le même état et valeur) x. Sinon, promise2 sera rempli avec la valeur de x.,
  • Si onFulfilled ou onRejected déclenche une exception e, promise2 doit être rejeté avec e comme la raison.
  • Si onFulfilled n’est pas une fonction et promise1 est remplie, promise2 doit être remplie avec la même valeur que promise1.,
  • Si onRejected n’est pas une fonction et promise1 est rejetée, promise2 doit être rejetée pour les mêmes raisons que la promise1.

Promise Chaining

parce que.then() renvoie toujours une nouvelle promesse, il est possible d’enchaîner les promesses avec un contrôle précis sur comment et où les erreurs sont gérées. Les promesses vous permettent d’imiter le comportement du code synchrone normal try/catch.,

comme le code synchrone, le chaînage entraînera une séquence qui s’exécute en série., En d’autres termes, vous pouvez faire:

fetch(url)
.then(process)
.then(save)
.catch(handleErrors)
;

Voici un exemple d’une chaîne de promesses complexe avec plusieurs rejets:

exemple de comportement de chaînage de promesses sur CodePen

gestion des erreurs

notez que les promesses ont à la fois un succès et un gestionnaire d’erreurs, et il est très courant de voir du code qui fait ceci:

save().then(
handleSuccess,
handleError
);

mais que se passe-t-il si handleSuccess() , La promesse renvoyée par .then() sera rejetée, mais il n’y a rien pour attraper le rejet — ce qui signifie qu’une erreur dans votre application est avalée. Oups!

pour cette raison, certaines personnes considèrent le code ci-dessus comme un anti-motif et recommandent plutôt ce qui suit:

save()
.then(handleSuccess)
.catch(handleError)
;

La différence est subtile, mais importante. Dans le premier exemple, une erreur provenant de l’opérationsave() sera interceptée, mais une erreur provenant de la fonctionhandleSuccess() sera avalée.,

Without .catch(), an error in the success handler is uncaught.

In the second example, .catch() will handle rejections from either save(), or handleSuccess().

With .catch(), both error sources are handled., (source du diagramme)

bien sûr, l’erreur save() peut être une erreur réseau, tandis que l’erreur handleSuccess() peut être due au fait que le développeur a oublié de gérer un code d’état spécifique. Et si vous voulez les gérer différemment? Vous pouvez choisir de les gérer tous les deux:

save()
.then(
handleSuccess,
handleNetworkError
)
.catch(handleProgrammerError)
;

quoi que vous préfériez, je recommande de terminer toutes les chaînes de promesses avec un .catch(). Cela vaut la peine de répéter:

je recommande de terminer toutes les chaînes de promesses avec un.catch().,

Comment annuler une promesse?

l’Une des premières choses nouvelle promesse utilisateurs se demandent souvent comment annuler une promesse. Voici une idée: rejetez simplement la promesse avec « annulé” comme raison. Si vous devez le gérer différemment d’une erreur” normale », effectuez votre branchement dans votre gestionnaire d’erreurs.

Voici quelques erreurs courantes que les gens font lorsqu’ils lancent leur propre annulation de promesse:

ajout .,cancel () à la promesse

L’ajout de .cancel() rend la promesse non standard, mais viole également une autre règle de promesses: seule la fonction qui crée la promesse doit pouvoir résoudre, rejeter ou annuler la promesse. L’exposer brise cette encapsulation et encourage les gens à écrire du code qui manipule la promesse dans des endroits qui ne devraient pas le savoir. Évitez les spaghettis et les promesses non tenues.

oubliant de nettoyer

certaines personnes intelligentes ont compris qu’il existe un moyen d’utiliserPromise.race() comme mécanisme d’annulation., Le problème est que le contrôle d’annulation est pris à partir de la fonction qui crée la promesse, qui est le seul endroit où vous pouvez effectuer des activités de nettoyage appropriées, telles que la suppression des délais d’attente ou la libération de mémoire en effaçant les références aux données, etc…

oublier de gérer une promesse d’annulation rejetée

saviez-vous que Chrome envoie des messages d’avertissement sur toute la console lorsque vous oubliez de gérer un rejet de promesse? Oups!

trop complexe

la proposition d’annulation du TC39 retirée proposait un canal de messagerie distinct pour les annulations., Il a également utilisé un nouveau concept appelé jeton d’annulation. À mon avis, la solution aurait considérablement gonflé la spécification de la promesse, et la seule caractéristique qu’elle aurait fournie que les spéculations ne prennent pas directement en charge est la séparation des rejets et des annulations, ce qui, OMI, n’est pas nécessaire pour commencer.

voulez-vous faire une commutation selon qu’il y a une exception ou une annulation? Oui, absolument. Est – ce le travail de la promesse? À mon avis, non, il n’est pas.,

repenser L’annulation de la promesse

généralement, je passe toutes les informations dont la promesse a besoin pour déterminer comment résoudre / rejeter / annuler au moment de la création de la promesse. De cette façon, il n’y a pas besoin d’une méthode .cancel() sur une promesse. Vous vous demandez peut-être comment vous pourriez savoir si vous allez annuler ou non au moment de la création de la promesse.

« Si Je ne sais pas encore s’il faut annuler ou non, comment saurai-je quoi transmettre lorsque je créerai la promesse?,”

Si seulement il y avait une sorte d’objet qui pourrait représenter un potentiel de valeur dans l’avenir… oh, attendez.

la valeur que nous transmettons pour indiquer s’il faut annuler ou non pourrait être une promesse elle-même. Voici à quoi cela pourrait ressembler:

annulable wait — essayez-le sur CodePen

Nous utilisons l’affectation des paramètres par défaut pour lui dire de ne pas annuler par défaut. Cela rend le paramètrecancel commodément facultatif., Ensuite, nous définissons le délai d’expiration comme nous le faisions auparavant, mais cette fois, nous capturons L’ID du délai d’expiration afin de pouvoir l’effacer plus tard.

nous utilisons la méthodecancel.then() pour gérer l’Annulation et le nettoyage des ressources. Cela ne s’exécutera que si la promesse est annulée avant qu’elle ait une chance de se résoudre. Si vous annulez trop tard, vous avez raté votre chance. Que le train a quitté la gare.

Remarque: Vous vous demandez peut-être à quoi sert la fonctionnoop(). Le mot noop signifie no-op, ce qui signifie une fonction qui ne fait rien., Sans cela, V8 lancera des avertissements: UnhandledPromiseRejectionWarning: Unhandled promise rejection. C’est une bonne idée de toujours gérer les rejets de promesses, même si votre gestionnaire est un noop().

Abstracting Promise Cancellation

c’est très bien pour unwait() timer, mais nous pouvons abstraire cette idée plus loin pour encapsuler tout ce dont vous devez vous souvenir:

  1. rejeter la promesse d’annulation par défaut — nous ne voulons pas annuler ou
  2. N’oubliez pas d’effectuer le nettoyage lorsque vous refusez pour les annulations.,
  3. rappelez-vous que le nettoyage onCancel peut lui-même générer une erreur, et cette erreur devra également être gérée. (Notez que la gestion des erreurs est omise dans l’exemple d’attente ci-dessus — c’est facile à oublier!)

créons un utilitaire de promesse annulable que vous pouvez utiliser pour envelopper n’importe quelle promesse., Par exemple, pour gérer les requêtes réseau, etc The la signature ressemblera à ceci:

speculation(fn: SpecFunction, shouldCancel: Promise) => Promise

La SpecFunction est exactement comme la fonction que vous passeriez dans le constructeur Promise, à une exception près — il faut un onCancel() gestionnaire:

SpecFunction(resolve: Function, reject: Function, onCancel: Function) => Void

notez que cet exemple n’est qu’une illustration pour vous donner l’essentiel de son fonctionnement. Il y a d’autres cas de bord que vous devez prendre en considération., Par exemple, dans cette version, handleCancel sera appelé si vous annulez la promesse une fois qu’elle est déjà réglée.

j’ai implémenté une version de production maintenue de cela avec des cas périphériques couverts comme la bibliothèque open source, la spéculation.

utilisons l’abstraction de bibliothèque améliorée pour réécrire l’utilitaire annulablewait() d’avant., Tout d’abord installer la spéculation:

npm install --save speculation

Maintenant, vous pouvez l’importer et l’utiliser:

cela simplifie un peu les choses, car vous n’avez pas à vous soucier de lenoop(), interceptant des erreurs dans votreonCancel(), fonction ou autres cas de bord. Ces détails ont été résumés par speculation(). Vérifiez – le et n’hésitez pas à l’utiliser dans de vrais projets.,

Extras De La Promesse js Native

l’objetPromise contient des éléments supplémentaires qui pourraient vous intéresser:

  • Promise.reject() renvoie une promesse rejetée.
  • Promise.resolve() renvoie une résolu promesse.
  • Promise.race() prend un tableau (ou tout itérable) et renvoie une promesse qui se résout avec la valeur de la première promesse résolue dans l’itérable, ou rejette avec la raison de la première promesse qui rejette.,
  • Promise.all() prend un tableau (ou tout itérable) et renvoie une promesse qui se résout lorsque toutes les promesses de l’argument itérable ont été résolues, ou rejetées avec la raison de la première promesse passée qui rejette.

Conclusion

Les promesses sont devenues partie intégrante de plusieurs idiomes en JavaScript, y compris la norme WHATWG Fetch utilisée pour la plupart des requêtes ajax modernes, et la norme asynchrone Functions utilisée pour rendre le code asynchrone synchrone.,

Les fonctions asynchrones sont au stade 3 au moment d’écrire ces lignes, mais je prédis qu’elles deviendront bientôt une solution très populaire et très couramment utilisée pour la programmation asynchrone en JavaScript — ce qui signifie qu’apprendre à apprécier les promesses sera encore plus important pour les développeurs JavaScript dans un proche avenir.

par exemple, si vous utilisez Redux, je vous suggère de consulter redux-saga: une bibliothèque utilisée pour gérer les effets secondaires dans Redux qui dépend des fonctions asynchrones dans toute la documentation.,

j’espère que même les utilisateurs expérimentés de promise comprendront mieux ce que sont les promesses et comment elles fonctionnent, et comment mieux les utiliser après avoir lu ceci.

Découvrir la Série

  • qu’est Ce qu’une Fermeture?
  • Quelle est la différence entre L’héritage de classe et L’héritage prototypique?
  • qu’est Ce qu’une Fonction Pure?
  • Qu’est-ce que la Composition des fonctions?
  • qu’est-Ce que la Programmation Fonctionnelle?
  • qu’est Ce qu’une Promesse?
  • compétences non techniques

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *