Tag: JavaScript

关于并发请求

日常开发中的并发请求,一般不会去特地控制,直接依次请求就好,或者使用 Promise.all() 包装一层,等待所有成功的结果。但是当并发请求的数量大起来后,如果不处理并发量,可能会造成较严重的后果。比如大文件上传的场景,一般大文件上传需要切片分传,如果文件较大,可能会被分成100个小块分别请求,那这种同时100条的并发请求,大概率会有以下几个问题: 浏览器会限制,首先浏览器会限制同一域名下的并发请求数量,100个请求是不会立刻并发执行的,会排队处理,可能导致部分请求等待较长时间才开始; 服务器压力,即使绕过了浏览器的并发请求数量,短时间内大量的请求也会给服务器带来较大压力; 网络拥堵,大量并发请求会占用较多的网络带宽,可能导致其他网络活动受到影响,影响用户体验。 所以我们需要对大并发请求做个控制,即限制每次并发的请求量,比如每次只请求5条,100条请求分20次完成。那么怎么实现这种效果呢? 我看好多人会用 Promise.all() 来处理,比如将100条异步请求分成20个数组,每个数组有5个请求,每次将一个请求数组传入 Promise.all()中,当这组数据请求完,再传入下一个数组,以此类推直至请求结束。这种做法在理论上确实可以解决并发问题,之所以是理论上,主要有两个问题: Promise.all() 不能保证所有的请求结束后再给出结束状态,一旦有一个请求错误,会直接返回错误信息,不管后面的成员还在不在请求中。这样本质上还是无法准确控制同时并发量; 即使每次传入的一组请求,都是成功状态返回,或者使用Promise.allSettled,保证所有的请求结束后才返回状态。那么还是有效率问题,因为它需要等待这组数据全部请求完才能开启下一轮,如果这一组数据有一个接口需要10秒,那么这组数据请求就需要多等10秒,显然这问题是很大的。 所以最终还是需要我们自己手动来封装一个并发请求控制,主要实现两个功能: 可以灵活控制并发数量; 请求应该是队列形式,当一组请求中的某一条请求结束了,应自动加入下一条请求,以保证最大的效率。 最后,根据需求,参考网上资料,我简单封装了一个并发请求方法,代码如下: function concurRequest(urls, maxNum) { return new Promise((resolve) => { if (urls.length === 0) { resolve([]) } // 请求的 url 索引 let index = 0 // 已完成请求的数量 let count = 0 // 请求到的结果,每项都是 promise const result = [] // 请求函数 async function request() { // 缓存原始的下标 const i = index // 当前要请求的 url const url = urls[index] // 获取完当前的请求地址后,递增下标,为下一次请求准备 index++ try { const res = await fetch(url) result[i] = res } catch (err) { result[i] = err } finally { count++ // 如果已请求的数量等于总请求数量,那么就返回结果 if (count === urls.length) { resolve(result) } if (index < urls.length) { request() } } } // 第一次触发请求 for (let i = 0; i < Math.min(maxNum, urls.length); i++) { request() } }) } concurRequest 方法可以传入两个参数,接口请求路径数组urls和并发数量maxNum,方法会按照传入的最大请求数来控制同时并发量,当某一个请求结束后,会按照接口数组中的顺序自动请求下一条,直至结束。当然这只是一个范例,我们可以根据实际开发需求改动,比如可以传入一组定义好的请求方法,这样每个请求方法的内部都可以灵活控制。
 · 6 min read 

检测对象是否存在循环引用

在使用 JSON.stringify() 的时候,如果对象存在循环引用,就会报错。利用这一点,我想到可以用来检测对象是否存在循环引用。代码实现如下: function hasCircularReference(obj) { try { JSON.stringify(obj) return false } catch (e) { return e.message.indexOf('Converting circular structure to JSON') > -1 } } 出于好奇,我在想是否可以手动实现一个用于检测循环引用的方法,在跟 GPT 沟通后,实现了如下代码: function hasCircularReference2(obj) { const seenObj = new Set() // 存储已经访问过的对象引用 function detect(obj) { if (seenObj.has(obj)) { // 判断是否访问过该对象,如果访问过则返回 true return true } seenObj.add(obj) // 如果没有访问过该对象,则将其添加到集合中 for (const key in obj) { if (obj.hasOwnProperty(key) && detect(obj[key])) { // 遍历判断对象属性是否包含循环引用,如果包含则返回 true return true } } return false } // 返回检测结果 return detect(obj) }