第 153 题:实现一个批量请求函数 multiRequest(urls, maxNum)
yygmind opened this issue · comments
要求如下:
- 要求最大并发数 maxNum
- 每当有一个请求返回,就留下一个空位,可以增加新的请求
- 所有请求完成后,结果按照 urls 里面的顺序依次打出
// 我先来,模拟图片加载过程
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
console.log(url, "加载完成");
resolve(img);
};
img.onerror = function() {
reject(new Error('Error at:' + url));
};
img.src = url;
})
}
function multiRequest(urls, maxNum) {
const firstMaxNum = urls.splice(0, maxNum);
let promises = firstMaxNum.map((url, index)=>{
return loadImg(url).then(()=>{
return index
})
})
return urls.reduce((res, cur)=>{
return res.then(()=>{
return Promise.race(promises)
}).then((idx)=>{
promises[idx] = loadImg(cur).then(()=>{
return idx
})
})
}, Promise.resolve()).then(()=>{
return Promise.all(promises)
})
}
multiRequest(urls, 4).then(()=>{
console.log('finish')
})
function multiRequest(urls, maxNum) {
const ret = [];
let i = 0;
let resolve;
const promise = new Promise(r => resolve = r);
const addTask = () => {
if (i >= arr.length) {
return resolve();
}
const task = request(urls[i++]).finally(() => {
addTask();
});
ret.push(task);
}
while (i < maxNum) {
addTask();
}
return promise.then(() => Promise.all(ret));
}
// 模拟请求
function request(url) {
return new Promise((r) => {
const time = Math.random() * 1000;
setTimeout(() => r(url), time);
});
}
通过count计数的形式,判断接口是否全部完成
class MultiRequest {
constructor(props) {
this.max = props.max || 10; //数量
this.urls = props.urls || []; //接口
this.count = 0; //总共完成数量
this.result = []; //接口请求完成集合
this.thenCbs = []; //调用then
this.run(props.urls, 0); //初始化
}
add(url) {
const taskLen = this.urls.filter(v => !isPromise(v));
if (taskLen > this.max) {
throw `最多支持${this.max}条`;
}
if (Array.isArray(url)) {
this.run(url, this.urls.length);
this.urls = [...this.urls, ...url];
return true;
}
return false;
}
run(_urls, _urlsLen) {
console.log(this.urls, _urls, _urlsLen, "------");
_urls.forEach((v, _i) => {
const _index = _i + _urlsLen;
return v
.then(res => {
this.count++;
this.result[_index] = res;
if (this.count === this.urls.length && this.thenCbs.length) {
this.thenCbs.map(_cb => _cb(this.result));
this.clear();
}
})
.catch(error => {
this.count++;
this.result[_index] = error;
console.log(error);
if (this.count === this.urls.length && this.thenCbs.length) {
this.thenCbs.map(_cb => _cb(this.result));
this.clear();
}
});
});
}
clear() {
this.count = 0;
this.result = [];
this.thenCbs = [];
this.urls = [];
}
then(cb) {
this.thenCbs = [...this.thenCbs, ...[cb]];
return this;
}
}
const multiRequest = new MultiRequest({
max: 10,
urls: [
loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd"),
loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd"),
loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
]
});
multiRequest.add([
loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
]);
multiRequest.add([
loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
]);
console.log(
multiRequest.add([
loadImg("https://mirror-gold-cdn.xitu.io/168e096b62f70298fbd")
])
);
console.log(
multiRequest
.then(_value => {
console.log(_value, "---1");
})
.then(_value => {
console.log(_value, "---1");
}),
"----multiRequest"
);
function isPromise(obj) {
if (Promise && Promise.resolve) {
return Promise.resolve(obj) === obj;
}
return false;
}
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
console.log(url, "加载完成");
resolve(img);
};
img.onerror = function() {
reject(url);
};
img.src = url;
});
}
借鉴了一些题解的实现,用例跑通了,有问题或者可优化的话请各位大佬指正。
解题的关键是:队列和递归
代码如下
function handleFetchQueue(urls, max, callback) {
const urlCount = urls.length;
const requestsQueue = [];
const results = [];
let i = 0;
const handleRequest = url => {
const req = fetchFunc(url)
.then(res => {
results.push(res);
})
.catch(e => {
results.push(e);
})
.finally(() => {
const len = results.length;
if (len < urlCount) {
// 完成请求就出队
requestsQueue.shift();
handleRequest(urls[++i]);
} else if (len === urlCount) {
"function" === typeof callback && callback(results);
}
});
requestsQueue.push(req);
// 只要满足就继续请求
if (requestsQueue.length <= max) {
handleRequest(urls[++i]);
}
};
handleRequest(urls[i]);
}
const req = (url:string):Promise<any> => {
return new Promise((resolve,reject) => {
const xhr = new XMLHttpRequest();
xhr.open('get',url,true); //这里第三个参数不能为false,会变成同步
xhr.onload = () => {
if(xhr.status === 200){
resolve(url)
}else{
reject(url)
}
}
xhr.send();
})
}
const multiRequest = (urls:string[],maxNum:number):Promise<any> => {
let i = 0;
const ret:any = []; // 完成集合
const executing:any = [];// 执行集合
const enqueue = ():Promise<void> => {
if(urls.length === i){ // 判断是否全部执行完
return Promise.resolve();
}
const p = Promise.resolve().then(() => req(urls[i++]));
ret.push(p);
const e = p.then(() => executing.splice(0,1));// 执行完从executin中剔除一个
executing.push(e);
let r = Promise.resolve();
if(executing.length >= maxNum){// 判断executing中的长度是否大于等于限制数maxNum
r = Promise.race(executing);
}
return r.then(() => enqueue());// 当 r = Promise.race 时会等到其中一个执行完才执行下一步
}
return enqueue().then(() => Promise.all(ret)) //全部执行完按顺序返回
}
multiRequest(Array.from({length:10},(u,i) => ('/api/test?' + i)),2)
.then(res => {
console.log(res) // ["/api/test?0", "/api/test?1", "/api/test?2", "/api/test?3", "/api/test?4", "/api/test?5", "/api/test?6", "/api/test?7", "/api/test?8", "/api/test?9"]
})
如果网络太好,建议将Network throttling 调成Fast 3G。
class Scheduler {
constructor(count) {
this.count = count;
this.task = [];
this.executing = 0;
}
add(promiseCreator) {
this.task.push(() => promiseCreator());
this.executing < this.count && this.runTask();
}
runTask() {
if (this.task.length === 0) return;
this.executing++
this.task.splice(0, 1)0.then(() => {
this.executing--
this.runTask();
})
}
}
const timeout = (time) => new Promise((resolve, reject) => {
setTimeout(resolve, time);
})
function multiRequest(urls = [], maxNum = 1) {
let scheduler = new Scheduler(maxNum);
const addTask = (time, order) => {
scheduler.add(() => timeout(time).then(() => console.log(order)))
}
for (let i = 0; i < urls.length; i++) {
addTask(1000, urls[i]);
}
}
借鉴了一些题解的实现,用例跑通了,有问题或者可优化的话请各位大佬指正。
解题的关键是:队列和递归
代码如下
function handleFetchQueue(urls, max, callback) { const urlCount = urls.length; const requestsQueue = []; const results = []; let i = 0; const handleRequest = url => { const req = fetchFunc(url) .then(res => { results.push(res); }) .catch(e => { results.push(e); }) .finally(() => { const len = results.length; if (len < urlCount) { // 完成请求就出队 requestsQueue.shift(); handleRequest(urls[++i]); } else if (len === urlCount) { "function" === typeof callback && callback(results); } }); requestsQueue.push(req); // 只要满足就继续请求 if (requestsQueue.length <= max) { handleRequest(urls[++i]); } }; }
这个不行吧,请求不是按顺序返回的,切要并发请求吧
// 我先来,模拟图片加载过程 function loadImg(url) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = function() { console.log(url, "加载完成"); resolve(img); }; img.onerror = function() { reject(new Error('Error at:' + url)); }; img.src = url; }) } function multiRequest(urls, maxNum) { const firstMaxNum = urls.splice(0, maxNum); let promises = firstMaxNum.map((url, index)=>{ return loadImg(url).then(()=>{ return index }) }) return urls.reduce((res, cur)=>{ return res.then(()=>{ return Promise.race(promises) }).then((idx)=>{ promises[idx] = loadImg(cur).then(()=>{ return idx }) }) }, Promise.resolve()).then(()=>{ return Promise.all(promises) }) } multiRequest(urls, 4).then(()=>{ console.log('finish') })
res是请求链接,res.then鞋油问题吧
说白了,就是实现一个限制并发的 Promise.all
目前设定的是,当执行过程中某个 promise 返回 reject 则停止后续的 promise 执行。
后续可以加上,abort 请求。可以是一个回调,失败时候把 executing 传递过去。回调去执行一些操作。
// Promise.all([promsie1(), promsie2(), promsie3()]);
// PromiseLimit([promsie1, promsie2, promsie3]);
function PromiseLimit(funcArray, limit = 5) {
let i = 0;
const result = [];
const executing = [];
const queue = function() {
if (i === funcArray.length) return Promise.all(executing);
const p = funcArray[i++]();
result.push(p);
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
return Promise.race(executing).then(
() => queue(),
e => Promise.reject(e)
);
}
return Promise.resolve().then(() => queue());
};
return queue().then(() => Promise.all(result));
}
// 测试代码
const result = [];
for (let index = 0; index < 10; index++) {
result.push(function() {
return new Promise((resolve, reject) => {
console.log("开始" + index, new Date().toLocaleString());
setTimeout(() => {
resolve(index);
console.log("结束" + index, new Date().toLocaleString());
}, parseInt(Math.random() * 10000));
});
});
}
PromiseLimit(result).then(data => {
console.log(data);
});
测试结果,图片不知道为啥不显示
开始0 2020/5/18 上午11:28:47
开始1 2020/5/18 上午11:28:47
开始2 2020/5/18 上午11:28:47
开始3 2020/5/18 上午11:28:47
开始4 2020/5/18 上午11:28:47
结束0 2020/5/18 上午11:28:47
开始5 2020/5/18 上午11:28:47
结束5 2020/5/18 上午11:28:51
开始6 2020/5/18 上午11:28:51
结束4 2020/5/18 上午11:28:53
开始7 2020/5/18 上午11:28:53
结束7 2020/5/18 上午11:28:53
开始8 2020/5/18 上午11:28:53
结束1 2020/5/18 上午11:28:54
开始9 2020/5/18 上午11:28:54
结束3 2020/5/18 上午11:28:54
结束2 2020/5/18 上午11:28:55
结束9 2020/5/18 上午11:28:55
结束6 2020/5/18 上午11:28:56
结束8 2020/5/18 上午11:29:01
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// 修改测试代码 随机失败或者成功
const result = [];
for (let index = 0; index < 10; index++) {
result.push(function() {
return new Promise((resolve, reject) => {
console.log("开始" + index, new Date().toLocaleString());
setTimeout(() => {
if (Math.random() > 0.5) {
resolve(index);
} else {
reject(index);
}
console.log("结束" + index, new Date().toLocaleString());
}, parseInt(Math.random() * 1000));
});
});
}
PromiseLimit(result).then(
data => {
console.log("成功", data);
},
data => {
console.log("失败", data);
}
);
开始0 2020/5/18 上午11:34:37
开始1 2020/5/18 上午11:34:37
开始2 2020/5/18 上午11:34:37
开始3 2020/5/18 上午11:34:37
开始4 2020/5/18 上午11:34:37
结束4 2020/5/18 上午11:34:37
失败 4
结束0 2020/5/18 上午11:34:37
结束3 2020/5/18 上午11:34:37
结束1 2020/5/18 上午11:34:37
结束2 2020/5/18 上午11:34:37
// 这题如果maxNum 为无限大,其实就是在让你实现Promise.all
// 如果是有一个失败就返回 就是Promise.race
function multiRequest(urls = [], maxNum) {
let result = new Array(urls.length).fill(false)
let sum = urls.length; //总数
let count = 0; //已完成数
return new Promise((resolve, reject) => {
//先请求maxNum个呗
while (count < maxNum) {
next()
}
function next() {
let current = count++
// 边界
if (current >= sum) {
!result.includes(false) && resolve(result)
return
}
let url = urls[current];
console.log("开始:" + current, new Date().toLocaleString());
fetch(url).then((res) => {
console.log("结束:" + current, new Date().toLocaleString());
result[current] = res
//还有未完成,递归;
if (current < sum) {
next()
}
}).catch((err) => {
console.log("结束:" + current, new Date().toLocaleString());
result[current] = err
if (current < sum) {
next()
}
})
}
})
}
let url2 = `https://api.github.com/search/users?q=d`;
let arr = new Array(100).fill(url2)
multiRequest(arr, 10).then((res) => {
console.log(res)
})
@SupermanWY
借鉴了你的写法,但是有个地方有bug,while循环那里,如果输入的urls的长度小于maxNun,就会一直执行了,所以需要限制一下
const fetch = (url) => {
return new Promise((rs) => {
const s = Math.random() * 1000
setTimeout(() => {
rs(url)
}, s)
})
}
const multiRequest = (urls, maxNum) => {
let allPromise = []
let len = urls.length
let resolve
let indirectPromise = new Promise((rs) => {
resolve = rs
})
let i = 0
let addFetch = () => {
if (i >= len) {
return resolve()
}
let tempP = fetch(urls[i++]).finally(() => {
addFetch()
})
allPromise.push(tempP)
}
// 这里需要限定一下
while (i < maxNum && i < len ) {
addFetch()
}
return indirectPromise.then(() => {
return Promise.all(allPromise)
})
}
multiRequest([
'/a',
'/b',
'/c',
'/d',
'/e',
'/f',
], 40).then((res) => {
console.log(res)
})
function multiRequest(urls, maxNum) {
let i = 0
let res = []
return new Promise(r => {
for(; i < maxNum; i++) {
addTask()
}
function addTask() {
res[i] = fetch(urls[i])
res[i].then(res => { i >= urls.length && r();i < urls.length && addTask(); i++; })
}
}).then((_) => {
Promise.all(res).then(ans => {
console.log(ans)
})
})
}
function fetch(url) {
return new Promise((resolve) => {
let start = new Date()
setTimeout(() => {
resolve(`start: ${start};end: ${new Date()}`)
}, 10000 * Math.random());
})
}
multiRequest([1,2,3,4,5,6,7,8,9], 3)
我第一感觉是:他让我写个promise.all
const apiPath = 'http://localhost:3000/';
let urlList = [${apiPath}test1
, ${apiPath}test2
, ${apiPath}test3
, ${apiPath}test4
,${apiPath}test5
];
function request(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('get', url, true); //这里第三个参数不能为false,会变成同步
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304) {
resolve(xhr.responseText)
}
}
}
xhr.send()
})
}
后台koa启个node服务 大概是
router.get('/test1', async (ctx) => {
let res = await delay(1000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test1 OK'
}
});
router.get('/test2', async (ctx) => {
let res = await delay(2000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test2 OK'
}
});
router.get('/test3', async (ctx) => {
let res = await delay(3000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test3 OK'
}
});
router.get('/test4', async (ctx) => {
let res = await delay(4000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test4 OK'
}
});
router.get('/test5', async (ctx) => {
let res = await delay(5000)
ctx.type = 'application/json';
ctx.body = {
status: 200,
msg: 'test5 OK'
}
});
function multiRequest(urls, maxNum = 8) {
let currentUrls;
let curIdx = 0
let currentResList = []
function next() {
currentUrls = urls.slice(curIdx,curIdx+maxNum)
return new Promise((resolve) => {
for (let item of currentUrls) {
request(item).then(res => {
currentResList.push(res)
// console.error(curIdx,currentResList,currentUrls)
if ( (currentResList.length)-curIdx == currentUrls.length ) {
curIdx = curIdx + currentUrls.length
if(curIdx < urls.length ){
next().then(resolve)
}else{
resolve(currentResList)
}
}
})
}
})
}
return new Promise(resolve=>{
next().then(res => {
resolve(res)
})
})
}
multiRequest(urlList, 2).then(res=>{
console.error(res,'rsss')
})
大概这样
function request (url) {
return new Promise (resolve => {
const time = Math.random() * 1000
setTimeout(() => resolve(`${url} + ${Date.now()}`), time)
})
}
function maxNum (urls, maxNum) {
this.maxNum = maxNum
this.urls = urls
this.requestArr = []
this.requestQueue = []
this.result = []
this.renderQueue = function () {
this.requestQueue = []
this.requestArr = []
this.requestQueue = this.urls.splice(0, this.maxNum)
this.renderRequest()
}
this.renderRequest = function () {
this.requestQueue.forEach(item => {
this.requestArr.push(request(item))
})
Promise.all(this.requestArr).then(res => {
this.result = this.result.concat(res)
if (this.urls.length) {
this.renderQueue()
} else {
this.printRes()
}
})
}
this.renderQueue()
this.printRes = function () {
console.log(this.result, 'result')
return this.result
}
}
let urls = ['www.test.com/1', 'www.test.com/2', 'www.test.com/3']
maxNum(urls, 2)
function multiRequest(urls, maxNum) {
return new Promise((resolve, reject) => {
const result = Array(urls.length);
let resNum = 0;
let promiseIndex = 0;
while (promiseIndex < maxNum) {
genNextFetch();
}
function genNextFetch() {
if (promiseIndex >= urls.length) return;
const curIndex = promiseIndex++;
fetch(urls[curIndex])
.then((res) => {
result[curIndex] = res;
resNum += 1;
if (resNum === urls.length) {
resolve(result);
} else {
genNextFetch();
}
})
.catch((err) => {
reject(err);
});
}
});
}
/**
* @param {Array<string>} urls
* @param {number} maxNum
* @return {Promise<Array<any>>}
*/
function multiRequest (urls, maxNum) {
const tasks = urls.map(url => request(url));
const tasksLen = tasks.length;
const result = [];
return new Promise((resolve, reject) => {
const runTask = (index) => {
if (result.filter(item => item).length === tasksLen) {
return resolve(result);
};
const min = Math.min(tasks.length, maxNum);
for (let i = 0; i < min; i++) {
const task = tasks.shift();
task.then(res => {
result[index + i] = res;
}).catch(reject).finally(() => {
runTask(min);
});
}
}
runTask(0);
});
}
const urls = ['request 1', 'request 2', 'request 3', 'request 4', 'request 5'];
multiRequest(urls, 4).then(res => {
console.log('result: ', res);
console.log('finish...');
});
// 模拟请求
function request(url) {
return new Promise((resolve, reject) => {
const time = Math.random() * 5000;
setTimeout(() => resolve(url), time);
});
}
function multiRequest(urls, maxNum) {
return new Promise((res, rej) => {
const tasksLength = urls.length
const result = []
let i = 0
let done = 0
function runTask() {
if(i >= tasksLength) return
const curIndex = i
const url = urls[curIndex]
fetch(url).then(res => res.json()).then((value) => {
result[curIndex] = value
done++
if(done === tasksLength) {
res(result)
return
}
runTask()
}).catch(err => {
rej(err)
})
i++
}
while(maxNum !== 0) {
runTask()
maxNum--
}
})
}
function request (url) { return new Promise (resolve => { const time = Math.random() * 1000 setTimeout(() => resolve(`${url} + ${Date.now()}`), time) }) } function maxNum (urls, maxNum) { this.maxNum = maxNum this.urls = urls this.requestArr = [] this.requestQueue = [] this.result = [] this.renderQueue = function () { this.requestQueue = [] this.requestArr = [] this.requestQueue = this.urls.splice(0, this.maxNum) this.renderRequest() } this.renderRequest = function () { this.requestQueue.forEach(item => { this.requestArr.push(request(item)) }) Promise.all(this.requestArr).then(res => { this.result = this.result.concat(res) if (this.urls.length) { this.renderQueue() } else { this.printRes() } }) } this.renderQueue() this.printRes = function () { console.log(this.result, 'result') return this.result } } let urls = ['www.test.com/1', 'www.test.com/2', 'www.test.com/3'] maxNum(urls, 2)
@sheepshine 题目中的条件2、每当有一个请求返回,就留下一个空位,可以增加新的请求, 直接用promise.all应该做不到吧?
async function multiRequest(urls, maxNum) {
let data = urls.map((url, index) => ({ index, url }))
let result = []
let promises = Array.from({ length: Math.min(maxNum,data.length) }, () => getChain(data, result))
await Promise.all(promises)
return result
}
async function getChain(data, res = []) {
while (data.length) {
let one = data.pop()
try {
let urlRes = await fetch(one.url)
res[one.index] = urlRes
}
catch (e) {
res[one.index] = e
}
}
}
// 调用
let arr = Array.from({ length: 100 }, () => 'https://www.baidu.com')
let finalRes = await multiRequest(arr, 5)
console.log('done', finalRes)
function multiRequest(urls, maxNum) { const ret = []; let i = 0; let resolve; const promise = new Promise(r => resolve = r); const addTask = () => { if (i >= arr.length) { return resolve(); } const task = request(urls[i++]).finally(() => { addTask(); }); ret.push(task); } while (i < maxNum) { addTask(); } return promise.then(() => Promise.all(ret)); } // 模拟请求 function request(url) { return new Promise((r) => { const time = Math.random() * 1000; setTimeout(() => r(url), time); }); }
const promise = new Promise(r => resolve = r);
这行看不懂。。。。好尴尬
递归调用来实现,这个想法是,最初您发送的请求数量上限为允许的最大值,并且这些请求中的每一个都应该在完成时继续递归发送,通过传入的索引来确定了urls 里面具体是那个URL,保证最后输出的顺序不会乱,而是依次输出;
function batchFetch(urls, concurrentRequestsLimit) {
return new Promise(resolve => {
var documents = [];
var index = 0;
function recursiveFetch(num) {
if (index === urls.length) {
return;
}
request(urls[index++]).then(r => {
documents[num] = r;
if (documents.length === urls.length) {
resolve(documents);
} else {
recursiveFetch(index);
}
});
}
for (let i = 0; i < concurrentRequestsLimit; i++) {
recursiveFetch(i);
}
});
}
var sources = [
'http://www.example_1.com/',
'http://www.example_2.com/',
'http://www.example_3.com/',
'http://www.example_4.com/',
'http://www.example_5.com/',
'http://www.example_6.com/'
];
// 测试用例
function request(url) {
return new Promise(r => {
const time = Math.random() * 1000;
setTimeout(() => r(url), time);
});
}
batchFetch(sources, 3).then(documents => {
console.log(documents);
});
function multiRequest(urls = [], maxNum) {
// 请求总数量
const sum = urls.length;
// 根据请求数量创建一个数组来保存请求的结果
const result = new Array(sum).fill(false);
// 当前完成的数量
let count = 0;
return new Promise((resolve, reject) => {
// 请求maxNum个
while (count < maxNum) {
next();
}
function next() {
let current = count++;
// 处理边界条件
if (current >= sum) {
// 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
!result.includes(false) && resolve(result);
return;
}
const url = urls[current];
console.log(`开始 ${current}`, new Date().toLocaleString());
fetch(url).then(res => {
// 保存请求结果
result[current] = res;
console.log(`完成 ${current}`, new Date().toLocaleString());
// 请求没有全部完成, 就递归
if (current < sum) {
next();
}
}).catch(err => {
console.log(`结束 ${current}`, new Date().toLocaleString());
result[current] = err;
// 请求没有全部完成, 就递归
if (current < sum) {
next();
}
});
}
});
}
const url = `https://www.baidu.com/s?wd=javascript`;
const urls = new Array(100).fill(url);
(async () => {
const res = await multiRequest(urls, 10);
console.log(res);
})();
看了下题目,不知道我这种实现对不对?没有验证过:
// 以 axios.post 为例
const multiRequest = (urls, maxNum) => {
return new Promise(resolve => {
let next = maxNum // 下一个准备发送的请求下标
let results = []
const p = (index) => {
axios.post(urls[index]).then(res => {
results[index] = res
const i = next
++next
if (i < urls.length) {
p(next)
}
if (results.length === urls.length) {
resolve(results)
}
})
}
for (let i = 0; i < urls.slice(0, maxNum); i++) {
p(i)
}
})
}
实现
/**
*
* @param {Array<() => any>} funcArray
* @param {number} limit 最大请求次数
*/
export function promiseLimit(funcArray, limit = 5) {
return new Promise((resolve) => {
let i = funcArray.length >= limit ? limit : funcArray.length
const result = []
function execFunc(func) {
if (i > funcArray.length) return resolve(result)
let p = func()
p = p instanceof Promise ? p : Promise.resolve(p)
p.then(
() => execFunc(funcArray[i++]),
() => execFunc(funcArray[i++])
)
result.push(p)
}
result.concat(funcArray.slice(0, i).map(func => execFunc(func)))
})
}
测试
function testPromiseLimit(base) {
let a = []
for (let i = 0; i < base; i++) {
a.push(() => {
console.log(`开始 ${i}`)
return new Promise((resolve, reject) => {
Math.random() > 0.5 ? setTimeout(() => resolve('success'), i * 500) : setTimeout(() => reject('fail'), i * 500)
})
})
}
return a
}
promiseLimit(testPromiseLimit(3))
promiseLimit(testPromiseLimit(5))
promiseLimit(testPromiseLimit(20))
感觉就是一个promiseAll的变种
var Fetch = url => {
return new Promise(resolve => {
setTimeout(() => {
resolve(url)
}, 1000)
})
}
var multiRequest = (urls, maxNum) => {
return new Promise((resolve, reject) => {
let resolvedNums = 0
let totalRequests = urls.length
let resolvedValues = new Array(totalRequests)
let urlIndex = maxNum - 1
const rfn = index => {
Promise.resolve(Fetch(urls[index])).then(value => {
resolvedNums++
urlIndex++
resolvedValues[index] = value
if(resolvedNums === totalRequests) {
return resolve(resolvedValues)
} else {
if(urlIndex < totalRequests) {
rfn(urlIndex)
}
}
})
}
for(let i = 0; i < maxNum; i++) {
rfn(i)
}
})
}
multiRequest(['api/1','api/2','api/3','api/4','api/5'], 2).then(res =>{
console.log(res)
})
大佬们,我这样写可行?
function multiRequest(urls, maxNum) {
return new Promise((resolve, reject) => {
var result = new Map()
for (var i = 0; i < maxNum; i++) {
fn()
}
function fn() {
if (urls.length) {
var url = urls.shift()
result.set(url, null)
return fetch(url).then(res => {
result.set(url, res)
fn()
}).catch(err => {
result.set(url, err)
fn()
})
} else {
resolve(result)
}
}
})
}
var urls = [
'https://avatars0.githubusercontent.com/u/26534692',
'https://avatars3.githubusercontent.com/u/19216813',
'https://avatars3.githubusercontent.com/u/4506712',
'https://avatars1.githubusercontent.com/u/6983391',
'https://avatars3.githubusercontent.com/u/22212189',
'https://avatars0.githubusercontent.com/u/22443771',
]
multiRequest(urls, 2).then(res => {
console.log(res)
})
function multiRequest(urls, maxNum) {
let len = urls.length, max = Math.min(len, maxNum), res = []
let start = 0, end = 0
return new Promise(resolve => {
for (let i = 0; i < max; i++) {
next(resolve, i)
}
})
function next(resolve, index) {
start++
if (start === len) {
return
}
fetch(urls[index])
.then(r => { res[index] = r })
.catch(e => { res[index] = e })
.finally(() => {
end++
if (end === len) {
resolve(res)
} else {
next(resolve, start)
}
})
}
}
const getRes = (url: string) => {
return new Promise(resolve => {
const random = Math.random() * 100;
setTimeout(() => {
resolve({url, random});
}, random);
});
};
const multiRequest = (urls: string[], maxNum: number): Promise<Array<string>> => {
// 执行队列
const ret: Promise<any>[] = [];
let i = 0,
resolve: () => void;
const promise = new Promise(r => (resolve = r));
const addTask = () => {
if (i >= urls.length) {
return resolve();
}
const task = getRes(urls[i++])
.then(res => {
console.log(res);
return res;
})
.finally(() => {
addTask();
});
ret.push(task);
};
while (i < maxNum) {
addTask();
}
return promise.then(() => Promise.all(ret));
};
multiRequest(['1', '2', '3', '4', '5', '6', '7', '8', '9'], 5);
// multiRequest(['123'], 3);
export default multiRequest;
const multiRequest = (urls, maxNum) => {
return new Promise((re, rj) => {
let reqs = urls.slice(0, maxNum)
let res = []
const singleReq = (req, index) => {
if(res.length == urls.length) {
return re(res)
}
Promise.resolve(req).then((v)=>{
res[index] = v
})
}
for(let i=0; i<reqs.length; i++){
Promise.resolve(reqs[i]).then((v)=>{
res[i] = v
singleReq(urls[maxNum], maxNum++)
})
}
})
}
function reqCeate(url) {
return new Promise((r) => {
const time = Math.random() * 1000;
setTimeout(() => r(url), time);
});
}
let reqs = [1,3,4,6,7,8,9]
reqs = reqs.map((v) => reqCeate(v))
multiRequest(reqs, 4).then(console.log)
var dataLists = [1,2,3,4,5,6,7,8,9,11,100,123];
const spreadLimit = (tasks, limitLen) => {
const mockReq = (task) => {
return new Promise((resolve, rj) => {
setTimeout(() => {
resolve()
console.log(task)
}, Math.random() * 2000)
})
};
const next = (task) => {
return mockReq(task).then(() => {
return (tasks.length == 0) ? 'done' : next(tasks.shift())
})
}
const arr = tasks.splice(0, limitLen).map((task) => next(task));
Promise.all(arr).then(() => console.log('done'));
}
spreadLimit(dataLists, 3)
class MutiRequest {
constructor(urls, max) {
this.urls = urls
this.max = max
this.pending = []
this.excuting = []
this.result = []
this.init(urls)
}
init(urls) {
for (let i = 0; i < urls.length; i++) {
this.add(fetchApi(urls[i]), i)
}
}
add(promiseFn, index) {
if (this.excuting.length === this.max) {
return new Promise((resolve) => {
this.pending.push(() => {
this.excute(promiseFn, resolve)
})
}).then((data) => {
console.log(index, data)
this.result[index] = data
this.printOut()
})
} else {
return new Promise((resolve) => {
this.excute(promiseFn, resolve)
}).then((data) => {
console.log(index, data)
this.result[index] = data
this.printOut()
})
}
}
printOut () {
if (this.result.length === this.urls.length) {
console.log(this.result)
}
}
excute(promiseFn, resolve) {
this.excuting.push(promiseFn)
promiseFn.then((data) => {
resolve(data)
this.excuting = this.excuting.filter((item) => {
return item !== promiseFn
})
if (this.pending.length) {
this.pending.shift()()
}
})
}
}
// 模拟请求
const fetchApi = (url) => {
return new Promise((resolve, reject) => {
let random = Math.random() * 10
setTimeout(() => {
resolve('res:' + url)
}, 1000 * random)
});
}
// 测试
let mutiTest = new MutiRequest([
'https://api.xx.com/01',
'https://api.xx.com/02',
'https://api.xx.com/03',
'https://api.xx.com/04',
'https://api.xx.com/05',
'https://api.xx.com/06',
'https://api.xx.com/07',
'https://api.xx.com/08',
'https://api.xx.com/09',
'https://api.xx.com/10',
'https://api.xx.com/11',
'https://api.xx.com/12',
'https://api.xx.com/13',
'https://api.xx.com/14',
'https://api.xx.com/15',
], 2)
用generator写一个
var multiRequest = (urls, max, callback) => {
return new Promise((resolve) => {
let limit = max
let currentTaskNums = 0
let finishedNum = 0
let index = 0
let res = []
function* genFn(urls) {
for (let i = 0; i < urls.length; i++) {
yield [fetch(urls[i]), i]
}
}
var gen = genFn(urls)
function step() {
currentTaskNums++
index++
let { value } = gen.next()
value[0].then((data) => {
res[value[1]] = data // 这个要处理下结果
currentTaskNums--
finishedNum++
if (index < urls.length) {
step()
} else if (index === urls.length && finishedNum === urls.length) {
callback()
resolve(res)
}
})
if (currentTaskNums < limit) {
step()
}
}
step()
})
}
multiRequest 实现
- ajax: 模拟接口请求
- push:压入对列
- commonHandle: 统一处理,出队和更新数据
- next:核心迭代器
- start:启动函数
function multiRequest(urls, maxNum) {
const now = Date.now();
// 结果队列
const result = [];
// 请求队列
const queue = [];
// 进行中队列
const pending = [];
// 模拟请求
const ajax = function (url) {
const random = Math.random() * 10;
const maxTimeout = 500 * 5;
const timeout = Math.floor(random * 1000);
return new Promise((resolve, reject) => {
setTimeout(() => {
if (timeout > maxTimeout) {
console.log(`success: [url: ${url}; timeout: ${timeout}, '耗时:' ${Date.now() - now}]`);
reject(`${url} fail: ${timeout}`);
} else {
console.log(`fail: [url: ${url}; timeout: ${timeout}, '耗时:' ${Date.now() - now}]`);
resolve(`${url} success: ${timeout}`);
}
}, timeout)
});
}
// 压入请求队列
const push = function (item) {
result.push(item);
queue.push(item.url);
}
// 统一处理
const commonHandle = (url, res) => {
// 更新result
result.forEach((item) => {
if (item.url === url) {
item.result = res;
}
})
const index = pending.indexOf(url);
pending.splice(index, 1);
}
return new Promise((resolve) => {
// 下一个
const next = function () {
if (result.filter(item => !item.result).length === 0) {
resolve(result);
return;
}
// 请求队列为空
if (queue.length === 0) {
return;
};
// 进行中队列小于maxNum
if (pending.length < maxNum) {
// 请求对列出
const url = queue.shift();
// 进行中对列进入
pending.push(url);
ajax(url)
.then((res) => {
commonHandle(url, res);
next()
}).catch((err) => {
commonHandle(url, err);
next()
})
}
}
// 请求
const request = function (item) {
push(item);
next();
}
// 启动
const start = function () {
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
const item = {
url,
result: '',
};
request(item);
}
}
start();
})
}
测试代码
function main() {
let urls = [];
for (let i = 0; i < 15; i++) {
urls.push(`url${i}`);
}
multiRequest(urls, 10).then((res) => {
console.log(res);
});
}
main();
面试被问到过,写一写:
function fetchLimit(urls, max) {
let num = 0;
let len = urls.length;
let _res = [];
function req(url) {
fetch(url)
.then(res => _res[num] = res)
.catch(err => _res[num] = err)
.finally(() => {
if (num >= len) return _res;
req(urls[num])
});
++num;
}
for (let i = 0; i < max; i++) {
req(urls[i])
}
}
function multiRequest(urls, nums) {
let result = {}
function fetchData(url, t) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result: ' + url)
}, t)
})
}
function request() {
return new Promise((resolve, reject) => {
for (let index = 0; index < urls.length; index++) {
const element = urls[index];
if (!result[element]) {
fetchData(element, 1000 * (index + 2)).then((r) => {
result[element] = r
if (Object.keys(result).length === urls.length) {
resolve(result)
}
})
}
}
})
}
function add(url) {
if (urls.length < nums) {
urls.push(url)
return request()
}
}
return{
add,
request
}
}
function test() {
let urls = ['a', 'b', 'c']
const nums = 5
const {add, request} = multiRequest(urls, nums)
function print(result) {
for (let index = 0; index < urls.length; index++) {
const element = urls[index];
console.log('index', index, 'url', element, 'result', result[element])
}
}
request().then(print)
setTimeout(() => {
add('d').then(print)
}, 4000)
}
test()
function multiRequest(urls, maxNum){
let res = [], idx = 0, count = 0, len = urls.length;
return new Promise((resolve, reject)=>{
let next = function(){
maxNum--;
Promise.resolve(urls[idx++]).then(val=> {
res[count++] = {status: 'fulfilled', value: val};
}, err=> {
res[count++] = {status: 'rejected', value: err};;
}).finally(()=> {
if(idx<len){
maxNum++;
next();
}else{
resolve(res)
}
})
}
while(idx < len && maxNum > 0){
next()
}
})
}
class Scheduler {
constructor(maxNum) {
//等待执行的任务队列
this.taskList = []
//当前任务数
this.count = 0
//最大任务数
this.maxNum = maxNum
}
async add(promiseCreator) {
//当当前任务数超出最大任务数就将其加入等待执行的任务队列
if (this.count >= this.maxNum) {
await new Promise(resolve => {
this.taskList.push(resolve)
})
}
this.count++
const result = await promiseCreator()
this.count--
//当其它任务执行完任务队列中还有任务没执行就将其出队并执行
if (this.taskList.length > 0) {
this.taskList.shift()()
}
return result;
}
}
// 模拟请求
function request(url) {
return new Promise((r) => {
const time = Math.random() * 1000;
setTimeout(() => r(url), time);
});
}
function multiRequest(urls, maxNum){
const requests = [];
const scheduler = new Scheduler(maxNum);
for(let i = 0, len = urls.length; i < len; i++) {
requests.push(scheduler.add(() => request(urls[i])))
}
Promise.all(requests).then((res) => res.forEach((r) => console.log(r)))
}
function multiRequest(urls, maxNum) {
const waitUrls = [] // 等待执行的url队列
function fetchUrl(url) {
delay(url).then((res) => {
console.log('res: ', res)
if (waitUrls.length) {
fetchUrl(waitUrls[0])
waitUrls.shift()
}
})
}
urls.forEach((url, index) => {
if (index < maxNum) {
fetchUrl(url)
} else {
waitUrls.push(urls[index])
}
})
}
/**
* test
*/
function delay(result) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(result)
}, 2000)
})
}
multiRequest(['xxx.json', 'yyy.json', 'zzz.json', 'qqq.json', 'www.json'], 2)
function request(url) {
const timeOut = Math.random() * 10;
const result = url +': ' + timeOut + 's';
return new Promise((res, rej) => {
try {
setTimeout(() => {
// console.log(url, result);
res(result);
}, timeOut * 1000);
} catch (error) {
console.error(url, error);
rej(error);
}
});
}
function multiRequest(urls, maxNum = 3) {
let requestedIndex = 0;
let currentReqestting = 0;
const results = {};
function pushRequest(url, callback) {
if (requestedIndex >= urls.length || currentReqestting >= maxNum) {
return;
}
request(url)
.then((res) => {
results[url] = res;
currentReqestting--;
if (Object.keys(results).length >= urls.length) {
callback();
}
pushRequest(urls[requestedIndex], callback);
})
.catch(e => {
results[url] = e;
currentReqestting--;
if (Object.keys(results).length >= urls.length) {
callback();
}
pushRequest(urls[requestedIndex], callback);
});
currentReqestting++;
requestedIndex++;
pushRequest(urls[requestedIndex], callback);
}
return new Promise((res, rej) => {
try {
if(requestedIndex < urls.length && currentReqestting < maxNum){
pushRequest(urls[requestedIndex],() => {
res({
urls,
results
});
});
}
} catch (error) {
rej({
urls,
error
});
}
});
}
multiRequest(['/sale_center/sale_order/', '/sale_center/klicen_order/', '/sale_center/terminal_order/', '/sale_center/xyt_order/', '/sale_center/patch_order/'])
.then(({ urls, results }) => {
console.log('------------------------------');
console.log(results);
urls.forEach(url => {
console.log(results[url]);
});
});
function multiRequest(urls, maxNum, callback) {
var arr = [];
var count = 0;
for (let i = 0; i < maxNum; i++) {
ajax(i)
}
function ajax(i) {
if (urls[i] !== undefined && urls[i] !== null) {
setTimeout(function () {
count++;
success(urls[i], i)
}, Math.random() * 10000)
}
}
function success(res, i) {
arr[i] = res;
if (count === urls.length) {
callback(arr);
} else {
let index = maxNum + count - 1
ajax(index)
}
}
}
function multiRequests(urls, maxNums) {
let total = urls.length;
let results = [];
return new Promise((resolve, reject) => {
let currIndex = 0; // 当前待执行的下标
let restNum = total; // 剩余未执行的数量
// 添加初始值
for (let i = 0; i < maxNums; i++) {
if (i < total) {
addRequest(request(urls[i]), i);
}
}
function addRequest(req, index) {
currIndex++;
req.then((res) => {
results[index] = res;
})
.finally(() => {
restNum--;
if (currIndex < total) {
let req = request(urls[currIndex]);
addRequest(req, currIndex);
} else if (restNum == 0) {
resolve(results);
}
});
}
});
}
// 实现一个批量请求函数 multiRequest(urls, maxNum)
function multiRequest(urls, maxNum) {
const result = []
const start = +new Date()
let resolvedCount = 0
let pendingCount = 0
let windowSize = 0
function generateP(url, pendingCount) {
return new Promise((res, rej) => {
setTimeout(() => {
res({
data: url,
index: pendingCount,
});
}, url * 1000);
});
}
return new Promise((r) => {
while (windowSize < maxNum) {
execP()
}
function execP() {
const p = generateP(urls[pendingCount], pendingCount);
windowSize++;
pendingCount++;
p.then(({index, data}) => {
resolvedCount++;
windowSize--;
result[index] = {
data,
time: +new Date() - start,
};
if (resolvedCount === urls.length) {
r(result);
} else {
if (pendingCount < urls.length) {
execP();
}
}
});
}
});
}
var start = +new Date()
multiRequest([3, 1, 4, 2], 2).then((data) => {
console.log(`time passed ${+new Date() - start}`, data);
})
function myFetch(url) {
const waitTime = Math.random() * 100 + 100;
return new Promise((resolve) => {
setTimeout(() => {
console.log(url);
resolve(url);
}, waitTime);
});
}
function multiRequest(urls, maxNum) {
return new Promise((resolve) => {
const results = Array.from(urls, () => null);
let numSuccess = 0;
let waitIdx = maxNum;
function triggerNext() {
if (waitIdx < urls.length) {
const thisIdx = waitIdx;
myFetch(urls[thisIdx]).then((r) => {
results[thisIdx] = r;
numSuccess++;
triggerNext();
});
waitIdx++;
}
if (numSuccess === urls.length) resolve(results);
}
for (let i = 0; i < maxNum; i++) {
myFetch(urls[i]).then((r) => {
results[i] = r;
numSuccess++;
triggerNext();
});
}
});
}
multiRequest([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 6).then((r) =>
console.log(r)
);
function myFetch(url) {
const waitTime = Math.random() * 100 + 100;
return new Promise((resolve) => {
setTimeout(() => {
console.log(url);
resolve(url);
}, waitTime);
});
}
function multiRequest(urls, maxNum) {
return new Promise((resolve) => {
const results = Array.from(urls, () => null);
let numSuccess = 0;
let waitIdx = maxNum;
function triggerNext() {
if (waitIdx < urls.length) {
const thisIdx = waitIdx;
myFetch(urls[thisIdx]).then((r) => {
results[thisIdx] = r;
numSuccess++;
triggerNext();
});
waitIdx++;
}
if (numSuccess === urls.length) resolve(results);
}
for (let i = 0; i < maxNum; i++) {
myFetch(urls[i]).then((r) => {
results[i] = r;
numSuccess++;
triggerNext();
});
}
});
}
multiRequest([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 6).then((r) =>
console.log(r)
);
function multiRequest(urls, maxNum) {
const dl = [];
const resArr = [];
let maxIndx = 0;
function getPromise(i, curIndex) {
maxIndx++;
return new Promise((resolve, reject) => {
console.log("开始" + curIndex, new Date().toLocaleString());
setTimeout(() => {
if (Math.random() < 0.5) {
resolve(curIndex);
} else {
reject(new Error(curIndex));
}
console.log("结束" + curIndex, new Date().toLocaleString());
}, 10000 * Math.random());
}).then(
(res) => {
return promiseHandler(res, i, curIndex);
},
(err) => {
return promiseHandler(err, i, curIndex);
},
);
}
function promiseHandler(res, i, curIndex) {
resArr[curIndex] = res;
if (maxIndx < urls.length) {
return getPromise(i, maxIndx);
}
return res;
}
for (let i = 0; i < maxNum; i++) {
dl.push(getPromise(i, i));
}
Promise.all(dl).then(() => {
console.log(resArr);
});
}
const req = (url:string):Promise<any> => { return new Promise((resolve,reject) => { const xhr = new XMLHttpRequest(); xhr.open('get',url,true); //这里第三个参数不能为false,会变成同步 xhr.onload = () => { if(xhr.status === 200){ resolve(url) }else{ reject(url) } } xhr.send(); }) } const multiRequest = (urls:string[],maxNum:number):Promise<any> => { let i = 0; const ret:any = []; // 完成集合 const executing:any = [];// 执行集合 const enqueue = ():Promise<void> => { if(urls.length === i){ // 判断是否全部执行完 return Promise.resolve(); } const p = Promise.resolve().then(() => req(urls[i++])); ret.push(p); const e = p.then(() => executing.splice(0,1));// 执行完从executin中剔除一个 executing.push(e); let r = Promise.resolve(); if(executing.length >= maxNum){// 判断executing中的长度是否大于等于限制数maxNum r = Promise.race(executing); } return r.then(() => enqueue());// 当 r = Promise.race 时会等到其中一个执行完才执行下一步 } return enqueue().then(() => Promise.all(ret)) //全部执行完按顺序返回 } multiRequest(Array.from({length:10},(u,i) => ('/api/test?' + i)),2) .then(res => { console.log(res) // ["/api/test?0", "/api/test?1", "/api/test?2", "/api/test?3", "/api/test?4", "/api/test?5", "/api/test?6", "/api/test?7", "/api/test?8", "/api/test?9"] })
如果网络太好,建议将Network throttling 调成Fast 3G。
比如maxNum设置为2时,第三个请求进来还是先执行了,是不是不对
/**
* 想法是先构造好请求的一个对象, 包含请求方法, 请求状态, 请求的响应, 通过传入的urls创建出一个请求队列, 第一次就发maxNum个请求,
* 然后在每个请求完成后, 从请求队列取出状态为0的去请求, 最后所有请求完则返回
*/
function multiRequest(urls, maxNum) {
return new Promise((resolve, reject) => {
const queenArr = urls.map(url => {
const tempObj = {
request: () => {
const t = Math.random() * 5000
tempObj.status = 1
console.log(queenArr.map(i => i.status));
setTimeout(() => {
tempObj.status = 2
tempObj.response = `请求的url为${url}, 延迟为${t}`
const req = getRequest()
if (req) {
req()
} else if(queenArr.filter(i => i.status === 1).length === 0) {
console.log(queenArr.map(i => i.status));
resolve(queenArr.map(i => i.response))
}
}, t)
},
status: 0, // 0, 1, 2, 表示请求状态
response: url
}
return tempObj
})
// 从请求队列中获取到第一个还未请求的任务
const getRequest = () => {
const reqTask = queenArr.filter(i => i.status === 0)[0]
return reqTask && reqTask['request']
}
for (let i = 0; i < maxNum; i++) {
const req = queenArr[i]
req && req.request()
}
})
}
const urls = ['1', '2', '3', '4', '5']
multiRequest(urls, 2).then((res) => {
console.log(res)
})
function request (i) {
return new Promise(rs => setTimeout(rs, i * 1000));
}
async function multiRequest (urls, maxNum) {
const results = [];
async function wrapRequest () {
let index = results.length;
results[index] = null;
results[index] = await request(urls[index]);
if (results.length < urls.length) wrapRequest();
}
for (let i = 0; i < maxNum; ++i) {
wrapRequest();
}
}
multiRequest([1,4,3,2,1,3], 3);
function multiRequest(urls, maxNum) {
return new Promise((resolve, reject) => {
const result = []; // 输出结果
let count = 0; // 当前有多少个请求
let finished = 0; // 已经完成的请求数
const copyUrls = urls.map((url, index) => ({
url,
index
}))
for (let i = 0; i < Math.min(urls.length, maxNum); i++) {
queueUrl();
}
function queueUrl() {
if (count < maxNum && copyUrls.length) {
count ++;
const {url, index} = copyUrls.shift();
const ajax = sendRequest(url).then(res => {
result[index] = res;
}).catch(res => {
result[index] = res;
}).finally(() => {
count --;
finished ++;
if (finished >= urls.length) {
resolve(result)
} else {
queueUrl();
}
});
}
}
})
}
function sendRequest(url) {
return new Promise((resolve, reject) => {
const random = Math.floor(Math.random() * 1000);
if (random < 200) {
reject('error...' + url);
} else {
setTimeout(() => {
resolve('resolve...' + url )
}, random);
}
})
}
multiRequest(['11','22','33','44'], 3).then(res => console.log(res))
class Scheduler { constructor(maxNum) { //等待执行的任务队列 this.taskList = [] //当前任务数 this.count = 0 //最大任务数 this.maxNum = maxNum } async add(promiseCreator) { //当当前任务数超出最大任务数就将其加入等待执行的任务队列 if (this.count >= this.maxNum) { await new Promise(resolve => { this.taskList.push(resolve) }) } this.count++ const result = await promiseCreator() this.count-- //当其它任务执行完任务队列中还有任务没执行就将其出队并执行 if (this.taskList.length > 0) { this.taskList.shift()() } return result; } } // 模拟请求 function request(url) { return new Promise((r) => { const time = Math.random() * 1000; setTimeout(() => r(url), time); }); } function multiRequest(urls, maxNum){ const requests = []; const scheduler = new Scheduler(maxNum); for(let i = 0, len = urls.length; i < len; i++) { requests.push(scheduler.add(() => request(urls[i]))) } Promise.all(requests).then((res) => res.forEach((r) => console.log(r))) }
this.taskList.push(resolve) 请问这里为什么添加resolve
function ajax(url) {
return new Promise(resolve => {
setTimeout(() => {
resolve(url)
}, 10000 * Math.random())
})
}
function multiRequest(urls, maxNums) {
return new Promise(resolve => {
const list = [...urls]
const result = new Map()
const pending = new Set()
const request = (url) => {
pending.add(url)
ajax(url).then((data) => onFulfilled(url, data), err => onFulfilled(url, err))
}
const onFulfilled = (url, data) => {
result.set(url, data)
pending.delete(url)
if (!pending.size) {
return resolve([...result.values()])
} else if (list.length) {
request(list.shift())
}
}
for (let i = 0; i < urls.length; i++) {
const url = urls[i]
result.set(url, null)
if (i <= maxNums) {
request(list.shift())
}
}
})
}
const urls = [
'https://www.hello1',
'https://www.hello2',
'https://www.hello3',
'https://www.hello4',
'https://www.hello5',
'https://www.hello6',
'https://www.hello7',
'https://www.hello8',
'https://www.hello9',
'https://www.hello10',
'https://www.hello11',
'https://www.hello12'
]
multiRequest(urls, 3).then(res => {
console.log('sucssss======')
console.log(res)
})
function multiRequest(urls, maxNum) {
return new Promise((resolve) => {
const roundLength = Math.ceil(urls.length / maxNum)
const groupTasks = []
function executTaskGroup (tasks) {
return Promise.all(tasks.map((url) => {
return request(url)
}))
}
for(let i = 0; i < roundLength; i++) {
const tasks = urls.slice(i * maxNum, i * maxNum + maxNum)
groupTasks.push(executTaskGroup(tasks))
}
Promise.all(groupTasks).then((res) => {
resolve(res.flat())
})
})
}
function request(url) {
return new Promise(resolve => {
setTimeout(() => {
resolve(url)
}, 10000 * Math.random())
})
}
直接用分頁的概念做,最後用 flat 把結果壓平
const multiRequest = async function (urls, maxNum) {
const result = [];
const refObj = { count: 0, index: 0 };
await new Promise((resolve) => {
// 同步启动maxNum个任务
while (refObj.index < maxNum && refObj.index < urls.length) {
sendRequest(urls, refObj.index, refObj, result, resolve);
refObj.index++;
}
});
return result;
}
async function sendRequest(urls, index, refObj, result, resolve) {
// 请求接口,在拿到返回值之后将值写到对应位置上
const response = await fetch(urls[index])
const res = await response.json();
result[index] = res;
// 此时已经结束了一个请求,判断是不是最后一个url,不是就再开一个请求
if (refObj.index < urls.length) {
sendRequest(urls, refObj.index, refObj, result, resolve);
refObj.index++;
}
// 全部完成之后resolve掉
refObj.count++;
if (refObj.count === urls.length) {
resolve();
}
}
function multiTask(proms, max) {
let sum = proms.length;
let count = 0;
let done = 0;
const res = [];
return new Promise((rs, rj) => {
while (count < max) {
next();
}
function next() {
const cur = Math.min(count++, sum);
if (cur >= sum) {
done === sum && rs(res);
return;
}
proms[cur].then(r => {
res[cur] = r;
}).catch(e => {
res[cur] = e;
}).finally(() => {
done++;
next();
})
}
});
}
function request(url) {
return new Promise((rs, rj) => {
setTimeout(() => rs(url), Math.random() * 1e3);
});
}
const proms = new Array(20).fill(1).map((_, n) => request(n));
multiTask(proms, 10).then(res => {
console.log(res)
})
控制异步请求的并行个数,内部通过 maxNum 和栈内异步请求是否为空,决定是否拉取最新的请求执行。
class Queue {
constructor() {
this.queues = [];
}
push(data) {
return this.queues.push(data);
}
shift() {
return this.queues.shift();
}
isEmpty() {
return this.queues.length === 0;
}
}
class TaskWrapper {
constructor(data) {
const rawData = data;
this.getRaw = () => rawData;
}
}
class TaskPool {
constructor(size) {
this.size = size;
this.queue = new Queue();
}
/**
* 添加任务到队列中
* @param fn
* @param args
* @returns Promise
*/
add(fn, args) {
return new Promise((resolve) => {
const task = new TaskWrapper({ resolve, fn, args });
this.queue.push(task);
// 根据 size 来初始并发执行指定数量的任务
// 当 size 为 0 时, 暂停任务的执行, 等待正在执行的异步任务完成后 size 自增
if (this.size) this.run();
});
}
/**
* taskFn 执行结果返回一个 Promise 对象实例
* @param taskFn Promise
* @param taskFnArgs any
* @return Promise
*/
runTask(taskFn, taskFnArgs) {
const taskFnResult = Promise.resolve(taskFn(taskFnArgs));
const end = () => {
this.size++;
this.pullTask();
};
taskFnResult.then(end, end);
return taskFnResult;
}
/**
* 分批次拉取队列中的任务执行
* 直到队列为空或者size为0
*/
pullTask() {
if (this.queue.isEmpty() || this.size === 0) return;
this.run();
}
/**
* 为调度运行真正的 task 前做准备
*/
run() {
this.size--;
const { taskResolve, taskFn, taskFnArgs } = this.getRaw();
taskResolve(this.runTask(taskFn, taskFnArgs));
}
/**
* 获取队头的任务
* @returns {{taskFn: *, taskResolve: *, taskFnArgs: *}}
*/
getRaw() {
const task = this.queue.shift();
const {
resolve: taskResolve,
fn: taskFn,
args: taskFnArgs,
} = task.getRaw();
return {
taskResolve,
taskFn,
taskFnArgs,
};
}
}
/**模拟Http请求的函数 */
const request = (url) => {
return new Promise((resolve) => {
console.log('发送 Http 请求');
// 请求 URL
setTimeout(() => {
resolve(url);
}, 2000);
});
};
/** 实现 */
function multiRequest(urls, maxNum) {
const taskPool = new TaskPool(maxNum);
return Promise.all(urls.map((url) => taskPool.add(request, url)));
}
/** 演示 */
multiRequest(['/api/user', '/api/banners', '/api/task'], 1).then((res) => {
console.log('res:', res);
});
演示三个请求,依次执行的效果,maxNum 为 1
/**
* 带并发限制的批量请求函数
* @param {*} urls
* @param {*} max
* @param {*} callback
*/
function multiFetch(urls, max, callback) {
const urlCount = urls.length;
const urlsWithIndex = urls.map((url, index) => ({ url, index }));
const result = [];
// 批量发起请求
for (let i = 0; i < urlCount && i < max; i++) {
request(urlsWithIndex.shift());
}
function request({ url, index }) {
fetch(url).then(res => {
result[index] = res;
}).catch(err => {
result[index] = err;
}).finally(() => {
// 进行下一个请求
if (urlsWithIndex.length) request(urlsWithIndex.shift());
// 全部请求完成
if (result.length === urlCount) callback(result);
});
}
}
// 这题如果maxNum 为无限大,其实就是在让你实现Promise.all // 如果是有一个失败就返回 就是Promise.race function multiRequest(urls = [], maxNum) { let result = new Array(urls.length).fill(false) let sum = urls.length; //总数 let count = 0; //已完成数 return new Promise((resolve, reject) => { //先请求maxNum个呗 while (count < maxNum) { next() } function next() { let current = count++ // 边界 if (current >= sum) { !result.includes(false) && resolve(result) return } let url = urls[current]; console.log("开始:" + current, new Date().toLocaleString()); fetch(url).then((res) => { console.log("结束:" + current, new Date().toLocaleString()); result[current] = res //还有未完成,递归; if (current < sum) { next() } }).catch((err) => { console.log("结束:" + current, new Date().toLocaleString()); result[current] = err if (current < sum) { next() } }) } }) } let url2 = `https://api.github.com/search/users?q=d`; let arr = new Array(100).fill(url2) multiRequest(arr, 10).then((res) => { console.log(res) })
这个let current = count++
位置不对,它应该是在执行成功或者失败的回调里。
function req(url) {
return new Promise((resolve) => {
console.log('start: ' + url)
setTimeout(() => {
console.log('end: ' + url)
resolve('res' + url)
}, Math.random() * 1000)
})
}
const URLS = new Array(25).fill(0).map((_, i) => {
return i.toString()
})
async function multiRequest(urls, maxNum) {
const inRequest = [] // 正在请求的任务的promise数组
const results = [] // 所有任务的promise数组
const inReqIndxs = [] // 正在请求的任务的index的数组 (仅仅为了log验证请求的并发情况)
for (let i = 0; i < urls.length; i++) {
const url = urls[i]
let p
// 在下一个请求任务准备进来时,先看下是否正在请求任务数量达到maxNum
if (inRequest.length === maxNum) {
// 如果是,则等待任意进行中的任务完成
await Promise.any(inRequest)
}
// 推入下一个任务
p = req(url)
inRequest.push(p)
inReqIndxs.push(i)
results.push(p)
console.log(inReqIndxs)
// 每个任务本身则需要在自身完成时退出任务数组
p.then((val) => {
inRequest.splice(inRequest.indexOf(p), 1)
inReqIndxs.splice(inReqIndxs.indexOf(i), 1)
results[i] = val
})
}
return Promise.all(results)
}
multiRequest(URLS, 5).then(results => {
console.log('final results: ' + results)
})
/*
start: 0
[ 0 ]
start: 1
[ 0, 1 ]
start: 2
[ 0, 1, 2 ]
start: 3
[ 0, 1, 2, 3 ]
start: 4
[ 0, 1, 2, 3, 4 ]
end: 4
[ 0, 1, 2, 3 ]
start: 5
[ 0, 1, 2, 3, 5 ]
end: 2
[ 0, 1, 3, 5 ]
...
start: 22
[ 17, 19, 20, 21, 22 ]
end: 20
start: 23
[ 17, 19, 21, 22, 23 ]
end: 17
start: 24
[ 19, 21, 22, 23, 24 ]
end: 19
end: 23
end: 22
end: 21
end: 24
final results: res0,res1,res2,res3,res4,res5,res6,res7,res8,res9,res10,res11,res12,res13,res14,res15,res16,res17,res18,res19,res20,res21,res22,res23,res24
*/
function mutiReqN(n){
let cur=0
return function next(qs){
while(cur < n && qs.length){
cur++;
const q = qs.shift();
q().then((r)=>{
cur--;
console.log('请求结束===>', r)
next(qs)
})
}
}
}
const muti2 = mutiReqN(2);
const np = (n, mark)=> ()=> {
console.log('发起请求', mark)
return new Promise(resolve=> setTimeout(()=>resolve(mark), 1000 * n))
};
muti2([
np(2, 1),
np(2, 2),
np(2, 3),
np(2, 4),
np(2, 5),
])
最烦这种题目,弯弯绕绕的地方不少,但实际项目中屌用没用。题目其实有个障眼法:那个最大连接数,这个只有在第一次请求时才有效,后面多个请求是分别返回的,不存在并发的情况(JS 是单线程的,及时是多个请求同时返回也需要排队)。
async function multiRequest(urls, maxNum) {
const urlCount = urls.length;
const result = {};
let sendCount = 0;
let returnCount = 0;
const doRequest = (url) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`request url: ${url}, success`);
resolve(`data of : ${url}`);
}, 1000);
});
};
return new Promise((resolve) => {
const fetchUrlData = (url) => {
sendCount++;
return doRequest(url).then((ret) => {
result[url] = ret;
returnCount++;
}).finally(() => {
if (sendCount < urlCount) {
fetchUrlData(urls[sendCount]);
} else {
if (returnCount === urlCount) {
resolve(result);
}
}
});
}
Promise.all(urls.slice(0, maxNum).map((url, idx) => {
return fetchUrlData(url);
}));
});
}
var urls = Array.from({ length: 20 }).map((i,idx) => idx + 1);
multiRequest(urls, 3).then((result) => {
console.log('result: ', result);
});