Promise 异步并发控制
前言
在前端开发过程中,经常会遇到需要同时执行多个异步函数的情况,例如在请求数据时需要同时发起多个 HTTP 请求或者在执行一些复杂的计算时需要同时执行多个线程。此时我们需要对同时执行的异步操作进行控制,以确保它们按照既定的顺序执行,而不是混乱无序的执行。
在 JavaScript 中,我们可以使用 Promise 对象来实现对并发异步操作的控制。本文将介绍 Promise 的异步并发控制机制,包括如何控制并发请求,如何限制并发数量以及如何优化并发性能。
Promise 的基本用法
在介绍 Promise 的并发控制之前,我们先来回顾一下 Promise 的基本用法。Promise 是一种异步编程的解决方案,可以更好地处理回调地狱和异常处理问题。一个 Promise 对象具有三种状态:pending
(进行中)、resolved
(已完成)和 rejected
(已失败)。当一个 Promise 对象处于 pending
状态时,我们可以注册通过 .then()
方法注册回调函数来处理它的结果。如果 Promise 对象已经 resolved
或 rejected
,则对应的回调函数会被立即调用。
示例代码:
// javascriptcn.com 代码示例 const promise = new Promise((resolve, reject) => { setTimeout(() => { const result = Math.random(); if (result < 0.5) { resolve(result); } else { reject(new Error('Result is too large!')); } }, 1000); }); promise .then(result => console.log(result)) .catch(error => console.error(error));
上面的代码中,我们创建了一个返回一个随机小数的 Promise 对象,如果随机数小于 0.5
,则 Promise 对象成功(resolved),返回随机数;否则 Promise 对象失败(rejected),返回错误信息。我们使用 .then()
方法来注册回调函数来处理其返回结果和使用 .catch()
方法来处理其异常情况。
Promise 并发控制的实现方法
并发请求控制
在实际开发中,经常会遇到需要同时向多个服务器请求数据的情况。如果直接使用多个异步函数发起请求并回调处理结果,容易导致请求乱序、相互干扰、请求次数过多等问题。对此,我们可以使用 Promise.all() 方法来实现并发请求控制。
Promise.all() 方法接收一个包含多个 Promise 对象的数组作为参数,它会同时发起多个异步请求,并在所有异步请求全部完成时返回所有异步请求的结果数组。如果其中某一个异步请求失败,则整个 Promise 对象也失败。
示例代码:
// javascriptcn.com 代码示例 const promises = []; promises.push(fetch('/api/user')); promises.push(fetch('/api/post')); promises.push(fetch('/api/comment')); Promise.all(promises) .then(([userResponse, postResponse, commentResponse]) => { console.log(userResponse.json()); console.log(postResponse.json()); console.log(commentResponse.json()); }) .catch(error => console.error(error));
上面代码中,我们使用了 fetch() 方法,它是一种基于 Promise 的网络请求方法。我们同时向不同的服务器进行了三次请求,并使用 Promise.all() 方法来控制异步请求。当所有请求都完成后,我们通过解构赋值的方法,从返回的结果数组中取出每一个请求的结果,并对其进行解析和处理。
并发请求数量控制
但是,使用 Promise.all() 方法发起的异步请求并没有进行数量限制,如果同时发起很多请求会导致服务器资源的浪费或者网络拥堵。因此,我们需要对并发请求进行数量限制,在同一时间内只能发起一定数量的请求。
对此,可以使用 Promise 的链式调用方式来控制并发数量。我们可以先定义一个控制并发数的队列,用来存储尚未开始执行的 Promise 对象,并通过 .then()
方法不断从队列中取出一定数量的 Promise 对象,并开始执行。
示例代码:
// javascriptcn.com 代码示例 const limit = 5; // 设置最大并发数为 5 const queue = []; // 定义队列 const urls = ['url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7', 'url8', 'url9', 'url10']; // 定义异步请求函数 function fetchUrl(url) { return new Promise((resolve, reject) => { fetch(url) .then(response => response.json()) .then(data => resolve(data)) .catch(error => reject(error)) }); } // 将要执行的异步请求推入队列 for (let i = 0; i < urls.length; i++) { const promise = fetchUrl(urls[i]); queue.push(promise); promise.finally(() => { const index = queue.indexOf(promise); queue.splice(index, 1); }); } // 控制并发数量 function runQueue(limit) { const promises = []; while (promises.length < limit && queue.length > 0) { const promise = queue.shift(); promises.push(promise); promise.then(() => runQueue(limit)); } if (promises.length === 0) { return Promise.resolve(); } return Promise.all(promises).then(() => runQueue(limit)); } runQueue(limit) .catch(error => console.error(error));
上面的代码中,我们定义了一个 limit
变量,用来设置最大并发数为 5,同时定义了一个 queue
数组作为执行异步操作的队列,调用 fetchUrl()
方法对 10 个不同的 URL 进行请求,并将每个 Promise 对象推入队列,设置在 Promise 对象结束执行后将其从队列中删除。
接下来,我们定义了一个 runQueue()
函数,用来控制并发执行,遍历队列并逐一执行 Promise 对象,同时设置在 Promise 对象执行结束后再次运行 runQueue()
函数控制并发数量。在队列中没有 Promise 对象时,使用 Promise.resolve() 表示 Promise 对象完成。
并发请求性能优化
在实际开发中,我们并不仅仅要关注控制并发数,更要关心如何优化并发性能,提高代码的执行效率和减少用户等待时间。
一个常见的性能优化方法是使用 Promise 的批量请求方式。我们可以将一组需要并发执行的异步操作分组进行请求,以降低每个异步操作的请求时间,从而提高整体的并发执行效率。同时我们还可以使用 Promise.race()
方法来监控时间超时,防止某个异步操作长时间无响应阻塞整个程序。
示例代码:
// javascriptcn.com 代码示例 const urls = ['url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7', 'url8', 'url9', 'url10']; const max = 2; // 每组最大并发数为 2 const timeout = 5000 // 批量请求超时时间 // 将 URLs 按组分配,每组不超过 max 个 function groupUrls(urls, max) { const result = []; while (urls.length > 0) { result.push(urls.splice(0, max)); } return result; } // 批量请求并控制并发数量 function requestByGroup(urls) { return Promise.all( urls.map(url => { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Request timeout: ${url}`)); }, timeout); fetch(url) .then(response => response.json()) .then(data => { clearTimeout(timeoutId); resolve(data); }) .catch(error => { clearTimeout(timeoutId); reject(error); }); }); }) ); } // 批量请求所有 URLs 并控制并发数量 function fetchAll(urls, max) { const groups = groupUrls(urls, max); return groups.reduce((prev, group) => { return prev.then(results => { return requestByGroup(group).then(data => results.concat(data)); }); }, Promise.resolve([])); } fetchAll(urls, max) .then(results => console.log('All results:', results)) .catch(error => console.error(error));
总结
本文介绍了使用 Promise 对象来实现并发控制的相关技巧和方法,包括并发请求控制、并发请求数量控制和并发请求性能优化。我们可以使用 Promise.all() 方法来控制并发请求,使用异步队列控制并发数量,使用分组请求和异步操作监测控制请求性能。通过灵活使用 Promise 对象实现并发控制,可以提高应用程序的效率性能和用户体验,同时使代码更加简洁易读,避免了回调地狱和异常处理问题。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/654fcbf27d4982a6eb8bf6e7