web-dev-qa-db-fra.com

Node.js: Quand utiliser Promises vs Callbacks

J'ai mis à jour un code Node.js plus ancien. Au cours du processus, je conçois de nouveaux modules pour travailler avec l'ancien code. Je constate que maintenant, contrairement à la première fois que j'ai écrit ceci, je me fie davantage à l'utilisation des promesses ES6 qu'à des rappels. Alors maintenant, j'ai ce mélange de fonctions qui renvoient des promesses et d'autres qui prennent des rappels - ce qui est fastidieux. Je pense que finalement, il devrait être remanié pour utiliser les promesses. Mais avant que cela soit fait ... 

Quelles sont les situations où les promesses sont préférées et où les rappels sont préférés?

Existe-t-il un type de situation qu'un rappel peut gérer mieux qu'une promesse et inversement?

D'après ce que j'ai vu jusqu'à présent, je ne vois vraiment aucune raison d'utiliser des rappels au lieu de promesses. Est-ce vrai?

15
Sean Lynch

Tout d'abord, vous ne voulez pratiquement jamais écrire du code combinant des rappels et des promesses d'opérations asynchrones. Si vous passez aux promesses ou si vous présentez des promesses, vous voudrez probablement reformuler les rappels de cette même section de code en promesses. Pour les types d'opérations appropriés, les avantages des promesses par rapport aux rappels en clair présentent tellement d'avantages que les efforts de conversion valent vraiment la peine d'être convertis lorsque vous travaillez déjà dans un domaine de code.

Les promesses sont bonnes pour:

  • Surveillance des opérations synchrones
  • Ce besoin de notifier une seule fois (généralement l'achèvement ou erreur)
  • Coordonner ou gérer plusieurs opérations asynchrones telles que le séquençage ou la ramification d'opérations asynchrones ou gérer plusieurs opérations en vol en même temps
  • Propagation d'erreurs à partir d'opérations asynchrones imbriquées ou profondément imbriquées
  • Préparer le code pour l'utilisation de async/wait (ou l'utiliser maintenant avec un transpiler)
  • Opérations qui correspondent au modèle Promise dans lequel il n'y a que trois états: pending, fulfilled et rejected et où l'état passe de pending => fulfilled ou de pending => rejected ne peut pas être modifié (une seule transition à sens unique).
  • Lier ou chaîner dynamiquement des opérations asynchrones (telles que ces deux opérations asynchrones, examinent le résultat, puis décident des autres opérations asynchrones à effectuer en fonction du résultat intermédiaire)
  • Gestion d'un mélange d'opérations asynchrones et synchrones
  • Récupérer et propager automatiquement vers le haut les exceptions qui se produisent dans les rappels d'achèvement asynchrone (dans les rappels en clair, ces exceptions sont parfois masquées de manière silencieuse).

Les rappels en clair sont bons pour des choses que les promesses ne peuvent pas faire:

  • Notifications synchrones (telles que le rappel pour Array.prototype.map())
  • Notifications qui peuvent survenir plus d'une fois (et doivent donc appeler le rappel plus d'une fois). Les promesses sont des dispositifs à prise unique et ne peuvent pas être utilisées pour les notifications répétées.
  • Situations qui ne peuvent pas être mappées dans le modèle d'état unidirectionnel rejeté, rempli, en attente.

Et, j'ajouterais aussi EventEmitter au mélange.

EventEmitters sont parfaits pour:

  • Publier/souscrire des notifications de type
  • Une interface avec un modèle d'événement, en particulier lorsque des événements peuvent se produire plusieurs fois (comme des flux)
  • Couplages lâches lorsque le code tiers souhaite participer ou surveiller quelque chose sans plus d'une API qu'un eventEmitter. Aucune API à concevoir. Il suffit de rendre public eventEmitter et de définir certains événements et les données qui les accompagnent.

Notes sur la conversion du code de rappel brut en Promises

Si vos rappels respectent la convention d'appel du nœud avec le rappel passé en tant que dernier argument et appelé comme suit: callback(err, result), vous encapsulerez automatiquement la fonction parent dans une promesse avec util.promisify() dans node.js ou si vous utilisez la bibliothèque bibliothèque de promesses Bluebird , avec Promise.promisify() .

Avec Bluebird, vous pouvez même promisifier un module entier (qui utilise des rappels asynchrones dans la convention d’appel de node.js) à la fois, par exemple:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.writeFileAsync("file.txt", data).then(() => {
    // done here
}).catch(err => {
    // error here
});

Dans node.js version 8+

Il existe maintenant util.promisify() qui convertira une fonction asynchrone utilisant la convention d’appel asynchrone node.js en une fonction renvoyant une promesse.

Exemple de la doc:

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);

// usage of promisified function
stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
});
27
jfriend00

Ils existent tous les deux pour résoudre le même problème, gérer le résultat d’une fonction asynchrone. 

Les rappels ont tendance à être plus détaillés et la coordination simultanée de plusieurs demandes asynchrones peut conduire à callback hell si vous ne modulez pas activement vos fonctions. La gestion et le suivi des erreurs ont tendance à être moins simples et même source de confusion, car il peut y avoir de nombreux objets d'erreur qui retournent tous à une seule erreur plus loin dans la pile d'appels. Les erreurs doivent également être renvoyées à l'appelant d'origine, ce qui peut également entraîner des problèmes de tête lors de la détermination de l'emplacement de l'erreur d'origine si des fonctions anonymes ont été utilisées dans la chaîne de rappel. L'un des avantages des rappels réside dans le fait qu'ils sont simplement d'anciennes fonctions et ne nécessitent aucune compréhension supplémentaire, à part la connaissance du fonctionnement d'une opération asynchrone.

Les promesses sont plus courantes car elles nécessitent moins de code, sont plus lisibles car elles sont écrites comme des fonctions synchrones, ont un seul canal d’erreur, peuvent gérer les erreurs renvoyées et/ util.promisify() ajouté dans la dernière version de Node.js, peut convertir erreur-premiers rappels en promesses. Il y a aussi async/await qui est fait son chemin dans Node.js maintenant aussi, et ils ont aussi une interface avec Promises.

Ceci est totalement basé sur l'opinion, donc c'est vraiment ce avec quoi vous êtes le plus à l'aise, mais Promises et async/await représentent l'évolution du rappel et améliorent l'expérience de développement asynchrone. Ce n'est pas une comparaison exhaustive, loin de là, mais plutôt un regard approfondi sur les rappels et les promesses.

4
peteb