Promise 实现 Ajax 并发请求控制
一道字节跳动的面试题:
实现一个批量请求函数 multiRequest(urls, maxNum),要求如下:
• 要求最大并发数 maxNum
• 每当有一个请求返回,就留下一个空位,可以增加新的请求
• 所有请求完成后,结果按照 urls 里面的顺序依次打出
比较简单,情景应该是:
- 第一次入队 maxNum(urls 长度更小时为 urls.length)个请求任务
- 入队同时立刻执行
- 执行完毕,在 result 的对应位置记录结果,并标记状态,将该任务出队,继续取下一个任务入队
- 此时产生循环:一次任务的执行完毕,对应下一次任务的开始
const multiRequest = (urls, maxNum) => {
// 记录url的起始顺序
const urlsCopy = [...urls];
// 每个请求的状态,初始 0
const state = new Array(urls.length).fill(0);
// 结果按顺序存储
const result = new Array(urls.length);
const taskQueue = [];
const queueLimit = Math.min(maxNum, urls.length);
while (enqueue(taskQueue, urls.shift()) < queueLimit) {}
function request(queue, url) {
let i = urlsCopy.indexOf(url);
console.log("开始请求任务" + i);
// 模拟一下fetch/axios请求,默认是resolve
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟一个异常
if (i === 5) {
reject(new Error(i + "出错了"));
}
resolve(url);
}, 10000 * Math.random());
}).then(
(value) => {
result[i] = value;
state[i] = 1;
console.log(i + "任务成功");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
},
(error) => {
state[i] = 1;
console.log(i + "任务失败");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
throw error;
}
);
}
// 将任务入队,并自动触发请求
function enqueue(queue = [], url) {
// 记录下队列长度
let len = queue.push(url);
let i = urlsCopy.indexOf(url);
console.log(i + "任务入队");
request(queue, url);
return len;
}
// 将任务出队,并入队下一个任务
function dequeue(queue = [], url) {
queue.splice(queue.indexOf(url), 1);
if (urls.length) {
enqueue(queue, urls.shift());
} else {
// 所有任务结束,终止promise并返回
if (state.indexOf(0) === -1) {
promise.resolve(result);
}
}
}
let promise = {
resolve: "",
reject: "",
};
return new Promise((resolve, reject) => {
promise.resolve = resolve;
promise.reject = reject;
});
};
Async/Await 结合 Promise 实现并发请求控制
替换 Promise 部分即可,和 then 链的写法相比神清气爽。
const multiRequest = (urls, maxNum) => {
// 记录url的起始顺序
const urlsCopy = [...urls];
// 每个请求的状态,初始 0
const state = new Array(urls.length).fill(0);
// 结果按顺序存储
const result = new Array(urls.length);
const taskQueue = [];
const queueLimit = Math.min(maxNum, urls.length);
while (enqueue(taskQueue, urls.shift()) < queueLimit) {}
function request(queue, url) {
let i = urlsCopy.indexOf(url);
console.log("开始请求任务" + i);
if (i === 5) {
// 模拟异步
setTimeout(async () => {
// async/await 中用try、catch处理异常
try {
await Promise.reject(i + "出错了");
} catch (e) {
state[i] = 1;
console.log(i + "任务失败");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
throw e;
}
}, 10000 * Math.random());
} else {
setTimeout(async () => {
result[i] = await Promise.resolve(url);
state[i] = 1;
console.log(i + "任务成功");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
}, 10000 * Math.random());
}
}
// 将任务入队,并触发请求
function enqueue(queue = [], url) {
let len = queue.push(url);
let i = urlsCopy.indexOf(url);
console.log(i + "任务入队");
request(queue, url);
return len;
}
// 将任务出队,并入队下一个任务
function dequeue(queue = [], url) {
queue.splice(queue.indexOf(url), 1);
if (urls.length) {
enqueue(queue, urls.shift());
} else {
// 所有任务结束,终止promise并返回
if (state.indexOf(0) === -1) {
promise.resolve(result);
}
}
}
let promise = {
resolve: "",
reject: "",
};
return new Promise((resolve, reject) => {
promise.resolve = resolve;
promise.reject = reject;
});
};
至于为什么是这样写?
function request(queue, url) {
let i = urlsCopy.indexOf(url);
console.log("开始请求任务" + i);
if (i === 5) {
setTimeout(async () => {
// async/await 中用try、catch处理异常
try {
await Promise.reject(i + "出错了");
} catch (e) {
state[i] = 1;
console.log(i + "任务失败");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
throw e;
}
}, 10000 * Math.random());
} else {
setTimeout(async () => {
result[i] = await Promise.resolve(url);
state[i] = 1;
console.log(i + "任务成功");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
}, 10000 * Math.random());
}
}
只是因为这样比较人性化,当然也可以这样写:
async function request(queue, url) {
let i = urlsCopy.indexOf(url);
console.log("开始请求任务" + i);
if (i === 5) {
await setTimeout(() => {
// 模拟 async/await 返回一个promise
// 既然是 promise 当然可以用 then/catch 捕获
Promise.reject(new Error(i + "出错了")).catch((error) => {
state[i] = 1;
console.log(i + "任务失败");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
});
}, 10000 * Math.random());
} else {
await setTimeout(() => {
Promise.resolve(url).then((value) => {
result[i] = value;
state[i] = 1;
console.log(i + "任务成功");
// 任务完成,执行出队,开启下一次任务
dequeue(queue, url);
});
}, 10000 * Math.random());
}
}
- Post link: https://blog.sticla.top/2021/09/29/multiple-requests/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.
GitHub Issues