废话
我们都知道浏览器在发送请求时是有最大并发请求数的。 如果我们的网页中有大量的资源需要同时加载,浏览器不会同时加载那些数据,而是会先发送一个请求,当有一个请求数据时得到答复后,将发出另一个请求。
这里的最大并发请求数不需要更大或者更小。 需要找到一个相对的平衡点。 因为并发请求数越小,请求之间的块数就会增加,导致页面加载过慢,影响用户。 体验一下,这就像从上海到南京旅游一样。 如果车次很少的话,这么大的人可能买不到票,导致我们推迟行程,等下一趟车回来再坐火车。 下一趟火车。
这是否意味着可以乘坐的列车数量(浏览器中的并发请求数)越大越好? 其实不是的,火车需要铁轨和工作人员,调度的事情必须处理好。 同样,并发数越大,服务器的压力就越大,解析请求的时间就越长。 因此,通过适当减少每列列车的车厢数量,可以在列车数量和运载能力之间找到平衡。 在实际项目中,就是将我们的js、css等文件进行合并,将多个请求合并为一个请求,以达到优化网站的目的。
(提示:此限制适用于同一域名,您可以使用不同的子域名来克服此限制。)
当前浏览器中的限制通常是 6。
那么我们是否可以通过代码实现类似的功能,并定义一种方法来控制最大并发请求数呢? 下次我们会尝试这样做。
其实还可以
目的
构造一个等待请求队列,根据我们设置的最大并发请求数max同时发起max请求。 当任何一个请求响应并返回一个值时,我们将立即发送队列中的下一个请求。 此推送将持续进行,直到所有请求均已发送。 可以缓存所有请求的返回结果,然后当所有请求全部响应完毕后,返回一个结果链表,将结果按照对应的队列顺序抛给调用者,并执行预设的反弹函数,如果存在的话。
创建请求
首先,我们需要模拟大量的异步请求。 事实上,我们选择 Promise 来处理这项工作。 为了简单起见,您可以创建一个通过函数执行的 Promise,并对 Promise 执行相应的异步操作。
function createPromise (num) {
console.log(`生成第${num}个promise`)
return function () {
return new Promise((resolve, reject) => {
let time = Math.floor(Math.random() * 90) + 10
console.log(`执行第${num}个promise,需耗时${time}毫秒`)
setTimeout(() => {
console.log(`已完成请求${num}`)
resolve({
order: num,
data: `结果${num}`
})
}, time)
})
}
}
为了演示方便,我们定义了一个createPromise函数,它返回一个要执行的函数。 该函数将返回一个promise,并传入一个num参数作为标识符来区分不同的异步操作。 实际项目中,可以直接对多个异步操作进行控制,不需要再包装一层函数。
上面的promise,我们定义了一个随机的socket请求时间,设置在10ms到100ms之间来模拟我们真实的请求环境,因为每个socket的实际访问时间是不同的。 然后使用setTimeout来模拟异步操作。 当socket在相应的时间段内完成时,将解析返回值以进行进一步处理。
这里resolve的结果是一个对象。 order属性用于指示异步操作的标识,data属性用于存储异步操作的结果。
[ok] 这里没有技术集中。 现在我们使用这个函数来创建我们需要的东西。
加入队列
接下来,我们使用循环将多次生成的函数返回值存储到队列中以供我们将来使用。
var queue = []
for (let i = 1; i < 11; i++) {
queue.push(createPromise(i))
}
好的,现在队列字段中存储了 10 个要执行的函数。 至此,我们的准备工作已经完成。
呵呵~
发送请求
异步请求队列已准备就绪,因此我们需要创建一个 limitRequest 函数来处理我们的初始请求。 我们希望这个函数应该这样调用。
limitRequest(queue, 4, function (data) {
console.log(data)
console.log("所有请求全部执行完毕")
})
limitRequest的第一个参数是我们刚刚创建的请求队列,第二个参数是我们要限制的最大并发请求数,第三个参数是所有请求执行完毕后的反弹函数。 该函数传入的参数是所有请求都会返回的。 值的链接列表,结果的位置对应于队列中相应请求的位置。
现在我们要构造这个函数,如何开始呢? 呵呵~,忍不住想进入正题了。 [泪流满面]
在任何项目中,代码级别是最不重要的。 当谈到编写代码时,劳动力是最便宜的。 从项目角度来说,生意! 商业! 商业! 是最重要的。 只有明确了业务,才能从更高的角度设计出优秀的系统。 从单一的问题或需求出发,经过不断的分析或推演得到的推论可以更好地指导我们的开发。
举个不恰当的例子,就像透过管子看豹子一样。 如果我们只通过一根管子看它旁边的物体,而不连接管子,仅仅看一眼之后,我们就会勾勒出它的形状。 根据我们的经验,我们会认为这是一只豹子,虽然它可能是一只黑斑狗或一只虎斑猫,甚至是其他一些植物或物体,但当我们发现我们错了时,我们需要纠正我们自己。 这就像我们写代码的时候,bug一遍又一遍地出现,这里看起来对,那里有问题。 如果我们一开始不急于做出推断,而是先将管子放在物体周围,从多个角度仔细观察后再进行推断,正确的概率会大大降低,但只需要极少的时间。稍后需要。 修复即可达到满意的效果。
(⊙o⊙)…
闭嘴,我真的很擅长水墨
言归正传,回忆一下刚才我们做了什么,分析一下我们要做的处理,发起多个请求→等待返回→追加请求→继续等待返回。 尽管这是一个不断出售控制权的问题。 所以我们考虑使用一个生成器来帮助我们完成这个任务,但是我们需要做好计数工作,即当请求数量达到最大值时,我们将停止发送请求。 当请求返回时,我们将继续恢复发送请求css通知,直到返回所有请求。 所有请求执行完毕后,所有得到的结果都会放入链表中并返回。 在此期间,我们会将获取到的结果缓存起来,放入链表中,最后通知生成器函数结束执行。 按照这个思路,请看下面的代码,我们就会发现很清楚了。 [灵感的闪现]
function limitRequest (q, max, cb) {
console.log(`请求最大并发数控制为${max}`)
let request = generatorRequest(q)
let sent = 0
let received = 0
let result = []
let size = Math.min(q.length, max)
function runIt () {
request.next().value.then(value => {
result[value.order - 1] = value.data
received++
if (sent < q.length) {
sent++
runIt()
} else if (received == q.length) {
request.return()
cb && cb(result)
}
})
}
while (sent < size) {
sent++
runIt()
}
}
天才不是我,是你,给自己鼓掌吧。 [钟声鼓掌]
目瞪口呆
GeneratorRequest函数就是我们刚才提到的生成器,我们用request来表示它的控制。 我们还不用担心这个问题,下面我们会提到。 send表示已发送的请求数,received表示已响应的请求数,result表示返回结果的链表。 Size用于取所有待处理请求数与设置的最大数的最小值。 下面是核心点。 runIt 函数。
request.next() 将执行生成器函数并释放yield表达式的控制,并将结果存储在yield值的旁边。 因为得到的结果是一个promise,我们可以通过then继续处理收到的结果,在工作中我们主要做了三件事:
缓存结果,是为了让结果对应起来,不能通过push来处理,因为我们很难控制结果返回的顺序。 如果没有全部执行完成,则继续执行runIt。 执行完成后,终止生成器css通知,然后执行传入的反弹函数,并将结果传回。
其中,while用于处理所有先发出的最大数量的请求,后续的调用可以通过runIt进行弹跳。
神奇发电机
function* generatorRequest (q) {
let t = 0
while (true) {
yield q[t++]()
}
}
嗯~,就这么几行,哈哈哈哈哈哈哈,虽然我这里偷懒直接初始化t然后让它简单的自增,但是根据生成器函数的性质,我们还是可以实现很多更精细更好玩的控制。
现在我们执行后只能得到第一张截图的反例的效果,已经达到了我们一开始给自己设定的要求。
散花
写在最后
本人能力有限,希望本次分享能够帮助到您,如有欺骗,敬请谅解!
祝你们甜蜜越来越好! ! ! [做鬼脸] 来吧~~~