承诺组合器:race、allSettled、any

6 阅读7分钟

Promise 组合器:race、allSettled、和any 随着 ECMAScript 2015 (ES6) 中 Promises 的引入,JavaScript 处理异步操作的方式发生了重大转变。这些对象为处理异步计算提供了一种强大的范例,与传统的基于回调的模式相比,其可读性和可维护性均有所提升。Promises 最强大的功能之一是组合器——一种接受多个 Promises 并提供多种解析策略的函数。本文将深入探讨三个重要的 Promise 组合器:Promise.race()、Promise.allSettled()和Promise.any(),以及它们的内部工作原理、边缘情况、实际应用和性能考量。

JavaScript 中 Promises 的历史背景 在 Promises 引入之前,JavaScript 中的异步编程主要由回调函数主导。虽然这种方法实现了函数式编程,但也带来了诸如“回调地狱”之类的重大挑战,导致代码越来越难以阅读和维护。

Promise 结构通过封装异步操作的最终完成(或失败)及其结果值,彻底改变了这一现状。它还引入了一种链接机制,使开发人员能够更优雅地处理一系列异步操作。

Promise API Promise API 的核心围绕以下状态构建:

待处理- 初始状态。操作仍在进行中。 已完成-操作已成功完成。 拒绝-操作失败。 const myPromise = new Promise((resolve, reject) => { // Asynchronous operation if (/* operation successful */) { resolve("Success!"); } else { reject("Failure!"); } }); Promise 组合器简介 组合器允许开发者在各种情况下使用多个 Promise。我们将探讨的这三个是Promise.race()、Promise.allSettled()和Promise.any()。

  1. Promise.race() 深入探究 Promise.race(iterable)接受一个可迭代的 Promise 对象,并返回一个单个的 Promise,该 Promise 在可迭代的其中一个 Promise 解决或拒绝后立即解决或拒绝,并带有该 Promise 的值或原因。

const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'First promise resolved') ); const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'Second promise resolved') );

Promise.race([promise1, promise2]).then((value) => { console.log(value); // Output: "Second promise resolved" }); 边缘情况 拒绝处理:如果第一个要解决的承诺被拒绝,则由此产生的承诺race()也会被拒绝。 const promise1 = new Promise((resolve) => setTimeout(resolve, 300, "Success!")); const promise2 = new Promise((_, reject) => setTimeout(reject, 200, "Failed!"));

Promise.race([promise1, promise2]) .then(console.log) .catch(console.error); // Output: "Failed!" 性能考虑 在最先获取结果至关重要的场景下,使用Promise.race()非常有利,例如从多个 URL 获取数据,而第一个响应是唯一需要的。但是,如果涉及多个 Promise,则务必确保资源得到妥善管理,否则可能会导致性能下降。

用例www.mytiesarongs.com 超时:可以使用来实现 Promises 的取消机制Promise.race()。 function fetchWithTimeout(url, timeout) { return Promise.race([ fetch(url), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout!')), timeout)) ]); } 2. Promise.allSettled() 深入探究 Promise.allSettled(iterable)返回一个在所有给定的承诺都已解决或拒绝后解决的承诺,并带有一个对象数组,每个对象描述每个承诺的结果。

const promises = [ Promise.resolve(3), new Promise((resolve, reject) => setTimeout(reject, 100, 'Error')), Promise.resolve(42) ];

Promise.allSettled(promises).then((results) => { console.log(results); // Output: [ // { status: "fulfilled", value: 3 }, // { status: "rejected", reason: "Error" }, // { status: "fulfilled", value: 42 } // ] }); 边缘情况 使用allSettled()可以收集所有 Promise 的结果,包括失败的结果。但是,开发者必须注意,结果的顺序与输入数组中 Promise 的顺序相对应,而与它们的完成顺序无关。

真实用例 allSettled()在以下场景中特别有用:

记录或报告多个数据获取任务的所有结果,无论成功或失败。 某些结果仍然有用的回退操作(如部分数据加载)。 性能考虑 与 一样Promise.race(),可迭代对象中的所有 Promise 都会并行启动。如果某些任务占用大量资源,则可能导致性能瓶颈。优化策略可能包括根据预期持续时间和重要性来确定哪些 Promise 需要优先运行。

  1. Promise.any() 深入探究 Promise.any(iterable)返回一个 Promise,只要可迭代对象中的一个承诺实现,该承诺就会解决;如果可迭代对象中没有承诺实现(即,它们都被拒绝),则该承诺就会拒绝。

const promiseA = Promise.reject("Error A"); const promiseB = Promise.reject("Error B"); const promiseC = new Promise((resolve) => setTimeout(resolve, 100, "Success C"));

Promise.any([promiseA, promiseB, promiseC]) .then(console.log) // Output: "Success C" .catch(console.error); // This will not execute 边缘情况 如果所有 Promise 都拒绝,Promise.any()将返回一个带有 的被拒绝的 Promise AggregateError,这是一种新类型的 Error 对象。 Promise.any([Promise.reject("Error 1"), Promise.reject("Error 2")]) .catch((e) => console.error(e instanceof AggregateError)); // true 性能考虑 与 一样Promise.race(),一旦 Promise 解析成功,其余 Promise 将继续执行。优化策略可能包括限制资源分配或使用 Worker 进行繁重计算。

真实用例 Promise.any()在涉及备用 API 或服务调用的场景中是有益的,因为回退是可以接受的。

async function fetchData(urls) { return Promise.any(urls.map(url => fetch(url))); } 高级实施技术 当 Promise 组合器与高级编码技术结合使用时,其真正威力才能得以实现:

使用 Promise.allSettled 和 Array.reduce 实现顺序执行 有时,您可能希望根据先前执行的结果按顺序处理 Promise。结合使用Promise.allSettled和Array.reduce,您可以优雅地管理此类流程。

const tasks = [ () => Promise.resolve('Task 1 completed'), () => Promise.reject('Task 2 failed'), () => Promise.resolve('Task 3 completed'), ];

tasks .map(task => () => task().catch(e => e)) .reduce((prev, curr) => prev.then(curr), Promise.resolve()) .then(result => console.log(result)); // "Task 1 completed", "Task 2 failed", "Task 3 completed" 使用重试机制处理超时 如果您有一个可能超时的异步函数,结合Promise.race()重试机制会很有用:

async function fetchWithRetry(url, retries = 3) { const fetchPromise = () => fetch(url); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject('Timeout!'), 1000));

for (let i = 0; i < retries; i++) { try { const response = await Promise.race([fetchPromise(), timeoutPromise]); return response; } catch (error) { console.error(Attempt ${i + 1} failed: ${error}); if (i === retries - 1) throw error; } } } Promise 组合器与其他方法的比较 虽然 Promise API 从根本上改变了异步编程,但诸如 async/await 语法或利用 RxJS 等库等替代方法可以提供不同的优势。

Async/Await:此功能利用 Promises 的语法糖,提供一种更清晰的方式来编写顺序异步代码。 async function fetchData() { try { const data1 = await fetch(url1); const data2 = await fetch(url2); return [data1, data2]; } catch (e) { console.error('Fetching failed:', e); } } RxJS:对于需要更复杂的反应式编程模式的应用程序,RxJS 允许通过 Observables 处理异步数据流,尽管会增加复杂性,但可以提供更强大、更细粒度的控制。 调试技巧和陷阱 想象一下,您有多个 Promise 需要仔细监控。未处理的拒绝可能会导致应用程序中出现未观察到的错误。您可以使用以下技巧:

承诺拒绝:始终将.catch()处理程序附加到您的承诺以优雅地管理错误。 const promise = someAsyncFunction() .then((result) => processResult(result)) .catch((error) => console.error('Error occurred:', error)); 使用 Promise Tracker:出于调试目的,实现一个跟踪每个 Promise 状态的实用程序。 function trackPromise(promise) { console.log('Promise started'); return promise.finally(() => console.log('Promise settled')); } 结论 Promise.race()Promise 组合符(例如、Promise.allSettled()和 )的出现,Promise.any()让 JavaScript 开发者能够高效而优雅地管理异步操作。将它们整合到你的编码工具包中,可以显著提升异步代码的清晰度、可维护性和性能。通过了解它们各自的行为、边缘情况、实际适用性以及性能影响,开发者可以自信地利用这些工具来处理高要求的异步模式。

要深入了解 Promise API,请参阅MDN 文档中的 promises 部分。对于更高级的主题,Douglas Crockford 撰写的《JavaScript: The Good Parts》一书强调了基于函数的组合,这对于有效地使用 JavaScript 中的异步模式至关重要。

通过理解和实现有效的 Promise 组合器,高级开发人员将能够构建可扩展、高效且有弹性的应用程序,能够轻松处理复杂的异步任务。以上内容由企业信息服务平台提供,致力于工商信用信息查询、企业风险识别、经营数据分析。访问官网了解更多:www.ysdslt.com