Écrit par Rosario De Chiara✏️
En JavaScript, les promesses sont un outil puissant pour gérer les opérations asynchrones, particulièrement utile dans les événements liés à l'interface utilisateur. Ils représentent une valeur qui n’est peut-être pas disponible immédiatement mais qui sera résolue à un moment donné dans le futur.
Les promesses permettent (ou devraient permettre) aux développeurs d'écrire du code plus propre et plus gérable lorsqu'ils traitent des tâches telles que les appels d'API, les interactions utilisateur ou les animations. En utilisant des méthodes telles que .then(), .catch() et .finally(), Promises permet de gérer de manière plus intuitive les scénarios de réussite et d'erreur, évitant ainsi le fameux « enfer des rappels ».
Dans cet article, nous utiliserons la nouvelle méthode (promise.withResolvers() de mars 2024) qui vous permet d'écrire du code plus propre et plus simple en renvoyant un objet contenant trois choses : une nouvelle promesse et deux fonctions, une pour résoudre la promesse. et l'autre pour la rejeter. Comme il s'agit d'une mise à jour récente, vous aurez besoin d'un runtime Node récent (v>22) pour exécuter les exemples de cet article.
Dans les deux morceaux de code fonctionnellement équivalents suivants, nous pouvons comparer l'ancienne approche et la nouvelle approche consistant à attribuer la méthode pour résoudre ou rejeter une promesse :
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
Dans le code ci-dessus, vous pouvez voir l'utilisation la plus traditionnelle d'une promesse : vous instanciez un nouvel objet de promesse, puis, dans le constructeur, vous devez attribuer les deux fonctions, résoudre et rejeter, qui seront invoquées lorsque nécessaire.
Dans l'extrait de code suivant, le même morceau de code a été réécrit avec la nouvelle méthode Promise.withResolvers(), et cela semble plus simple :
const { promise, resolve, reject } = Promise.withResolvers(); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
Ici vous pouvez voir comment fonctionne la nouvelle approche. Il renvoie la promesse, sur laquelle vous pouvez appeler la méthode .then() et les deux fonctions, résoudre et rejeter.
L'approche traditionnelle des promesses encapsule la logique de création et de gestion des événements dans une seule fonction, ce qui peut être limitant si plusieurs conditions ou différentes parties du code doivent résoudre ou rejeter la promesse.
En revanche, Promise.withResolvers() offre une plus grande flexibilité en séparant la création de la promesse de la logique de résolution, ce qui la rend adaptée à la gestion de conditions complexes ou de plusieurs événements. Toutefois, pour les cas d'utilisation simples, la méthode traditionnelle peut être plus simple et plus familière à ceux qui sont habitués aux modèles de promesses standards.
Nous pouvons désormais tester la nouvelle approche sur un exemple plus réaliste. Dans le code ci-dessous, vous pouvez voir un exemple simple d'invocation d'API :
function fetchData(url) { return new Promise((resolve, reject) => { fetch(url) .then(response => { // Check if the response is okay (status 200-299) if (response.ok) { return response.json(); // Parse JSON if response is okay } else { // Reject the promise if the response is not okay reject(new Error('API Invocation failed')); } }) .then(data => { // Resolve the promise with the data resolve(data); }) .catch(error => { // Catch and reject the promise if there is a network error reject(error); }); }); } // Example usage const apiURL = '<ADD HERE YOU API ENDPOINT>'; fetchData(apiURL) .then(data => { // Handle the resolved data console.log('Data received:', data); }) .catch(error => { // Handle any errors that occurred console.error('Error occurred:', error); });
La fonction fetchData est conçue pour prendre une URL et renvoyer une promesse qui gère un appel d'API à l'aide de l'API fetch. Il traite la réponse en vérifiant si l'état de la réponse est compris entre 200 et 299, indiquant le succès.
En cas de succès, la réponse est analysée au format JSON et la promesse est résolue avec les données résultantes. Si la réponse échoue, la promesse est rejetée avec un message d'erreur approprié. De plus, la fonction inclut une gestion des erreurs pour détecter toute erreur réseau, rejetant la promesse si une telle erreur se produit.
L'exemple montre comment utiliser cette fonction, montrant comment gérer les données résolues avec un bloc .then() et gérer les erreurs à l'aide d'un bloc .catch(), garantissant que la récupération réussie des données et les erreurs sont gérées de manière appropriée.
Dans le code ci-dessous, nous réécrivons la fonction fetchData() en utilisant la nouvelle méthode Promise.withResolvers() :
function fetchData(url) { const { promise, resolve, reject } = Promise.withResolvers(); fetch(url) .then(response => { // Check if the response is okay (status 200-299) if (response.ok) { return response.json(); // Parse JSON if response is okay } else { // Reject the promise if the response is not okay reject(new Error('API Invocation failed')); } }) .then(data => { // Resolve the promise with the data resolve(data); }) .catch(error => { // Catch and reject the promise if there is a network error reject(error); }); return promise; }
Comme vous pouvez le voir, le code ci-dessus est plus lisible, et le rôle de l'objet Promise est clair : la fonction fetchData renverra une Promise qui sera résolue avec succès ou échouera, en invoquant – dans chaque cas – la méthode appropriée . Vous pouvez trouver le code ci-dessus sur le référentiel nommé api.invocation.{old|new}.js.
Le code suivant explore comment implémenter une méthode d'annulation de promesse. Comme vous le savez peut-être, vous ne pouvez pas annuler une promesse en JavaScript. Les promesses représentent le résultat d'une opération asynchrone et elles sont conçues pour être résolues ou rejetées une fois créées, sans mécanisme intégré pour les annuler.
Cette limitation survient parce que les promesses ont un processus de transition d'état défini ; ils commencent comme en attente et, une fois réglés, ne peuvent pas changer d’état. Ils sont censés encapsuler le résultat d’une opération plutôt que de contrôler l’opération elle-même, ce qui signifie qu’ils ne peuvent pas influencer ou annuler le processus sous-jacent. Ce choix de conception maintient Promises simple et axé sur la représentation du résultat final d'une opération :
const cancellablePromise = () => { const { promise, resolve, reject } = Promise.withResolvers(); promise.cancel = () => { reject("the promise got cancelled"); }; return promise; };
In the code above, you can see the object named cancellablePromise, which is a promise with an additional cancel() method that, as you can see, simply forces the invocation of the reject method. This is just syntactic sugar and does not cancel a JavaScript Promise, though it may help in writing clearer code.
An alternative approach is to use an AbortController and AbortSignal, which can be tied to the underlying operation (e.g., an HTTP request) to cancel it when needed. From the documentation, you can see that the AbortController and AbortSignal approach is a more expressive implementation of what we implemented in the code above: once the AbortSignal is invoked, the promise just gets rejected.
Another approach is to use reactive programming libraries like RxJS, which offers an implementation of the Observable pattern, a more sophisticated control over async data streams, including cancellation capabilities.
When speaking about practical use cases, Promises are well-suited for handling single asynchronous operations, such as fetching data from an API. In contrast, Observables are ideal for managing streams of data, such as user input, WebSocket events, or HTTP responses, where multiple values may be emitted over time.
We already clarified that once initiated, Promises cannot be canceled, whereas Observables allow for cancellation by unsubscribing from the stream. The general idea is that, with Observables, you have an explicit structure of the possible interaction with the object:
This is demonstrated in the code below:
import { Observable } from 'rxjs'; const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); const observer = observable.subscribe({ next(x) { console.log('Received value:', x); }, complete() { console.log('Observable completed'); } }); observer.unsubscribe();
This code cannot be rewritten with Promises because the Observable returns three values while a Promise can only be resolved once.
To experiment further with the unsubscribe method, we can add another Observer that will use the takeWhile() method: it will let the Observer wait for values to match a specific condition; in the code below, for example, it keeps receiving events from the Observable while the value is not 2:
import { Observable, takeWhile } from 'rxjs'; const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); const observer1 = observable.subscribe({ next(x) { console.log('Received by 1 value:', x); }, complete() { console.log('Observable 1 completed'); } }); const observer2 = observable.pipe( takeWhile(value => value != "2") ).subscribe(value => console.log('Received by 2 value:', value));
In the code above, observer1 is the same as we have already seen: it will just subscribe and keep receiving all the events from the Observable. The second one, observer2, will receive elements from the Observable while the condition is matched. In this case, this means when the value is different from 2.
From the execution, you can see how the two different mechanisms work:
$ node observable.mjs Received by 1 value: 1 Received by 1 value: 2 Received by 1 value: 3 Observable 1 completed Received by 2 value: 1 $
In this article, we investigated the new mechanism to allocate a Promise in JavaScript and laid out some of the possible ways to cancel a Promise before its completion. We also compared Promises with Observable objects, which not only offer the features of Promises but extend them by allowing multiple emissions of events and a proper mechanism for unsubscribing.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Try it for free.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!