事情是这样,上周四给团队专栏发了一篇文章,发完以后忘了把帐号切回本身的帐号。而后下午搬砖中场休息的时候刷掘金,在首页看到一篇关于限制并发请求的文章,以为有意思就点开看了。我扫了一眼文章中的题目,没有看文章内容,而后就开始本身解题了。尴尬的是我审错题了,一开始用团队帐号在评论里写了个 naive 的答案。然而更尴尬的是在我意识到本身审错题以后,还写不出正确答案……编程
题目是这样的:数组
忽略我一开始那个 naive 的答案,在我准备正确地解这个题的时候,想的是写一个通用方法,限制异步请求能并发执行的次数。那个通用函数写出来了,长这样:promise
const limitConcurrency = (fn, max) => {
const pendingTasks = new Set();
return async function(...args) {
while (pendingTasks.size >= max) {
await Promise.race(pendingTasks);
}
const promise = fn.apply(this, args);
const res = promise.catch(() => {});
pendingTasks.add(res);
await res;
pendingTasks.delete(res);
return promise;
};
};
复制代码
这个 limitConcurrency
函数在闭包里记录了当前还没 resolve 的 promise,而后在 while
循环里判断当前进行中的异步操做是否达到了设定的上限;若是达到了就用 Promise.race
等最快的那个异步执行完;当并发的异步操做数量没有达到上限时,继续执行当前的异步操做,并将当前的异步操做加到 pendingTasks 里面,在当前异步操做 resolve 的时候再将其从 pendingTask 里面删掉。闭包
而后再回到题目,一开始我思惟比较局限,想着我必需要等最后一个异步请求执行完再执行回调。怎么判断全部请求都执行完了呢?我又用了一个 naive 的方法,当 urls 数组里面最后一个请求执行完了,我就当全部请求执行完了。我最后这样写的:并发
function sendRequest(urls, max, callback) {
const limitFetch = limitConcurrency(fetch, max);
async function go(urlList){
const [head, ...tail] = urlList;
if(tail.length === 0) {
await limitFetch(head);
return callback();
}
limitFetch(head);
go(tail);
}
go(urls);
}
复制代码
动脑子想想也知道最后一个请求不必定是最后执行完。可是我卡在这里了,没办法了,而后我发沸点求助了,而后各路英雄各显神通,看到他们的答案我感到怀疑人生,这么简单的问题我怎么就卡壳了?app
精彩答案有不少,这里我只挑出我以为最精彩的答案,来自幻☆精灵:异步
function sendRequest(urls, max, callback) {
const len = urls.length;
let idx = 0;
let counter = 0;
function _request() {
// 有请求,有通道
while (idx < len && max > 0) {
max--; // 占用通道
fetch(urls[idx++]).finally(() => {
max++; // 释放通道
counter++;
if (counter === len) {
return callback();
} else {
_request();
}
});
}
}
_request();
}
复制代码
这个答案如此精巧简洁,我今天上午看到这个答案,而后出去登山,整个途中都在回味这段代码的精妙…… 它用最少的信息表达了异步和并发的本质。写出这种代码,须要的不单单是编程技巧,更多的是对异步和并发的理解。在我完整理解了这段代码以后,晚上回到家再来看我以前的答案,发现我离正确答案就只差一丢丢了!async
最终完整答案以下:函数
const limitConcurrency = (fn, max) => {
const pendingTasks = new Set();
return async function(...args) {
while (pendingTasks.size >= max) {
await Promise.race(pendingTasks);
}
const promise = fn.apply(this, args);
const res = promise.catch(() => {});
pendingTasks.add(res);
await res;
pendingTasks.delete(res);
return promise;
};
};
async function sendRequest(urls, max, callback) {
const limitFetch = limitConcurrency(fetch, max);
await Promise.all(urls.map(limitFetch));
callback();
}
复制代码
limitConcurrency
已经保证了个人所有请求的并发上限,我只须要用 Promise.all
来处理每一个并发频道的最后请求就好了……post
从代码简洁性来看,固然是幻☆精灵前辈的解法更优,但我以为个人解法也不无可取之处。我提供的答案最大的好处是,它抽象出了一个通用函数,这个通用函数能复用。