作者:Rosario De Chiara✏️
在 JavaScript 中,Promises 是处理异步操作的强大工具,在 UI 相关事件中特别有用。它们代表的值可能无法立即获得,但会在未来某个时刻得到解决。
Promise 允许(或应该允许)开发人员在处理 API 调用、用户交互或动画等任务时编写更清晰、更易于管理的代码。通过使用 .then()、.catch() 和 .finally() 等方法,Promises 能够以更直观的方式处理成功和错误场景,避免臭名昭著的“回调地狱”。
在本文中,我们将使用新的(2024 年 3 月)Promise.withResolvers() 方法,该方法允许您通过返回一个包含三件事的对象来编写更干净、更简单的代码:一个新的 Promise 和两个函数,一个用于解析 Promise另一个拒绝它,因为这是最近的更新,您将需要最新的 Node 运行时 (v>22) 来执行本文中的示例。
在以下两个功能等效的代码块中,我们可以比较旧方法和分配方法来解析或拒绝 Promise 的新方法:
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
在上面的代码中,你可以看到 Promise 最传统的用法:实例化一个新的 Promise 对象,然后在构造函数中,你必须分配resolve和reject这两个函数,这两个函数将在以下情况下被调用:需要。
在下面的代码片段中,相同的代码块已使用新的 Promise.withResolvers() 方法重写,并且看起来更简单:
const { promise, resolve, reject } = Promise.withResolvers(); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
在这里您可以看到新方法的工作原理。它返回 Promise,您可以在该 Promise 上调用 .then() 方法和两个函数:resolve 和reject。
传统的 Promise 方法将创建和事件处理逻辑封装在单个函数中,如果多个条件或代码的不同部分需要解析或拒绝 Promise,这可能会受到限制。
相比之下,Promise.withResolvers() 通过将 Promise 的创建与解析逻辑分离,提供了更大的灵活性,使其适合管理复杂的条件或多个事件。然而,对于简单的用例,传统方法对于那些习惯于标准承诺模式的人来说可能更简单、更熟悉。
我们现在可以在更现实的示例上测试新方法。在下面的代码中,您可以看到一个 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); });
fetchData 函数旨在获取 URL 并返回 Promise,该 Promise 使用 fetch API 处理 API 调用。它通过检查响应状态是否在 200-299 范围内(表示成功)来处理响应。
如果成功,响应将被解析为 JSON,并且 Promise 将使用结果数据进行解析。如果响应不成功,则 Promise 会被拒绝,并显示相应的错误消息。此外,该函数还包括错误处理以捕获任何网络错误,如果发生此类错误则拒绝 Promise。
该示例演示了如何使用此函数,展示了如何使用 .then() 块管理已解析的数据并使用 .catch() 块处理错误,确保成功的数据检索和错误得到适当的管理。
在下面的代码中,我们使用新的 Promise.withResolvers() 方法重写了 fetchData() 函数:
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; }
如您所见,上面的代码更具可读性,并且 Promise 对象的作用很明确:fetchData 函数将返回一个将成功解析或将失败的 Promise,在每种情况下调用正确的方法。您可以在名为 api.inspiration.{old|new}.js.
的存储库中找到上面的代码以下代码探讨了如何实现 Promise 取消方法。如您所知,您无法取消 JavaScript 中的 Promise。 Promise 代表异步操作的结果,它们被设计为一旦创建就解决或拒绝,没有内置机制来取消它们。
出现这个限制是因为 Promise 有定义的状态转换过程;它们一开始处于待定状态,一旦解决,就无法更改状态。它们旨在封装操作的结果,而不是控制操作本身,这意味着它们不能影响或取消底层过程。这种设计选择使 Promise 保持简单,并专注于表示操作的最终结果:
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.
以上是掌握 JavaScript 中的承诺取消的详细内容。更多信息请关注PHP中文网其他相关文章!