在前端开发中,Promise.all 方法是一个非常常用的函数,它可以将多个 Promise 对象组合成一个 Promise 对象。当所有 Promise 对象都成功完成时,Promise.all 的返回值会依次包含所有 Promise 返回值组成的数组;如果有任意一个 Promise 失败或抛出异常,则 Promise.all 返回的 Promise 也会立即失败。由于 Promise.all 的实现方式,我们可能会遇到重复调用的问题,本文将详细介绍这个问题和解决方法。
问题描述
在 ECMAScript 2020 中,Promise.all 方法的行为和之前版本基本相同,但是在使用 Promise.all 处理多个 Promise 时,如果其中一个 Promise 发生错误或被拒绝 (rejected),而其他 Promise 尚未完成,此时可以进行重试。然而,如果使用 Promise.all 进行重试,可能会导致其内部的 Promise 对象被重复调用,从而导致代码逻辑出现问题。
下面是一个示例,展示如何使用 Promise.all 实现 HTTP 请求:
let urls = ['http://example.com', 'http://example.org', 'http://example.net']; let promises = urls.map(url => fetch(url)); Promise.all(promises) .then(responses => { for (let response of responses) { console.log(`${response.url}: ${response.status}`); } }) .catch(error => console.error(error));
这段代码使用了 fetch 函数发送 HTTP 请求,并将返回的 Promise 对象存入了数组 promises 中。然后,程序使用 Promise.all 来等待所有请求完成,并将每个请求的响应结果输出到控制台上。与此类似,我们可以使用 Promise.all 来处理任何形式的异步操作。
问题分析
假设我们现在启动了一个 HTTP 请求,但由于某种原因,这个请求被挂起了,而我们又再次调用了该请求,此时问题就出现了。在这种情况下,Promise.all 内部的两个 Promise 对象实际上是相同的,因此当其中一个 Promise 被完成时,另一个 Promise 也会被完成。
这意味着,Promise.all 的 then 方法将在两次调用后立即执行,而不是在所有请求完成后执行。在上面的示例中,如果我们遇到类似的情况,会看到两次相同的输出,这可不是我们想要的结果。
因此,我们需要找到一种解决方案,以在重试 Promise.all 方法时避免重复调用 Promise 对象,这将有助于确保代码逻辑的正确性。
解决方案
为了解决 Promise.all 的重复调用问题,可以通过以下方式之一进行更改:
- 缓存 Promise.all 内部的 Promise 对象,以便在出现重复调用时能够避免重复使用这些 Promise 对象。
let urls = ['http://example.com', 'http://example.org', 'http://example.net']; function fetchUrls(urls) { let promises = urls.map(url => fetch(url)); return Promise.all(promises); } fetchUrls(urls) .then(responses => { for (let response of responses) { console.log(`${response.url}: ${response.status}`); } }) .catch(error => console.error(error));
在这个函数中,我们将 fetch 请求封装在一个函数内,并将 Promise.all 的结果返回。这样,如果我们需要重复调用该函数来重新发送请求,我们可以确保每次都会获取新的 Promise 对象,从而避免重复调用。
- 通过使用 Promise.race 方法,我们可以在指定的时间内等待 Promise 对象完成。如果一个 Promise 对象在指定的时间内未完成,则可以执行一个备用逻辑,或者再次发送请求。
let urls = ['http://example.com', 'http://example.org', 'http://example.net']; function fetchWithTimeout(url, timeout) { return Promise.race([ fetch(url), new Promise((resolve, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }) ]); } let promises = urls.map(url => fetchWithTimeout(url, 10000)); Promise.all(promises) .then(responses => { for (let response of responses) { console.log(`${response.url}: ${response.status}`); } }) .catch(error => console.error(error));
在这个函数中,我们使用了 Promise.race 方法来同时等待 HTTP 请求完成和超时。如果请求成功返回,Promise.race 的返回值就是请求的 Promise 对象,这样我们就可以将该 Promise 传递给 Promise.all。如果请求超时,Promise.race 的返回值将变成一个拒绝 (rejected) 状态的 Promise 对象,这样我们就可以进行备用逻辑或再次发送请求。
总结
Promise.all 是一个非常有用的函数,它可以将多个 Promise 对象组合成一个 Promise 对象。但是,由于其内部的实现方式,我们有可能会遇到重复调用的问题。本文介绍了这个问题和解决方法,我们可以选择缓存 Promise.all 内部的 Promise 对象,或通过使用 Promise.race 方法来等待 HTTP 请求完成并处理超时情况。这些技巧都有助于我们确保代码逻辑的正确性,并提高代码复用的效率。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65aa46ffadd4f0e0ff3e1afa