前端一面:并发控制、重试与轮询题目精讲
在“异步/网络”这一块,一面常见的进阶题集中在:并发数控制、请求重试策略、轮询等。这一篇挑几道典型题,重点讲清楚思路而不是堆代码。
并发限制:同一时间最多跑 N 个请求
这类题的典型描述是:
有一组异步任务(通常是返回 Promise 的函数),要求同一时间最多并发执行 N 个,全部完成后返回结果。
高层思路:
- 维护一个队列存待执行任务;
- 维护一个计数器
running表示当前运行中的数量; - 当
running < limit时,从队列取任务执行,并在任务完成后递减计数,再触发下一任务。
比起记代码,更重要的是你能否先把这三点讲清楚。
请求重试:失败后按一定策略再试几次
重试题常见的变体:
- 固定重试次数(例如最多 3 次);
- 带延迟/指数退避(每次重试等待时间增加);
- 区分“可重试错误”和“不可重试错误”(如 5xx vs 4xx)。
常见思路:
- 把“执行一次请求 + 必要等待”的逻辑封装成一个递归或循环;
- 用一个计数器记录已经尝试的次数,超过上限就直接 reject;
- 可以根据错误类型决定是否继续重试。
轮询:定期请求直到满足条件或超时
轮询题通常描述为:
每隔一段时间请求一次接口,直到返回某个状态或达到最大尝试次数。
高层思路:
- 使用
setTimeout或setInterval控制节奏; - 在回调中判断是否满足条件:
- 满足 → resolve,停止轮询;
- 不满足 → 如果未达最大次数,则安排下一次调用;否则 reject/给出超时提示。
这类题重点是控制逻辑清晰,而不是用某个“巧妙 API”一行写完。
常见面试题与参考答案
题 1:实现一个带并发限制的请求调度器
题目示例:实现一个
Scheduler,构造时传入最大并发数,add方法接收一个返回 Promise 的函数,要求同一时间最多执行limit个任务。
参考实现思路(可简化):
1class Scheduler {
2 constructor(limit) {
3 this.limit = limit;
4 this.running = 0;
5 this.queue = [];
6 }
7
8 add(task) {
9 return new Promise((resolve, reject) => {
10 const run = () => {
11 this.running++;
12 task()
13 .then(resolve, reject)
14 .finally(() => {
15 this.running--;
16 if (this.queue.length > 0) {
17 const next = this.queue.shift();
18 next();
19 }
20 });
21 };
22
23 if (this.running < this.limit) {
24 run();
25 } else {
26 this.queue.push(run);
27 }
28 });
29 }
30}
答题时可以强调:
- 用队列 + 计数器来做“流量控制”;
finally中递减计数并触发队列里的下一个任务;add返回的也是 Promise,方便调用方用await聚合结果。
题 2:实现一个带重试的请求函数
题目示例:封装一个
requestWithRetry(fn, times, delay),失败时最多重试times次,每次间隔delay毫秒。
参考实现(简化版):
1function sleep(ms) {
2 return new Promise((resolve) => setTimeout(resolve, ms));
3}
4
5async function requestWithRetry(fn, times = 3, delay = 1000) {
6 let lastError;
7 for (let i = 0; i < times; i++) {
8 try {
9 return await fn();
10 } catch (e) {
11 lastError = e;
12 if (i < times - 1) {
13 await sleep(delay);
14 }
15 }
16 }
17 throw lastError;
18}
可以顺带提到的点:
- 实战中可以根据错误类型决定是否重试(例如只对网络错误/5xx 重试);
- 也可以采用指数退避策略(delay 逐次增加)来避免打爆服务。
题 3:实现一个简单的轮询函数
题目示例:实现一个
poll(fn, interval, maxTimes),定期调用fn,直到返回某个条件或达到最大次数。
参考实现思路:
1function poll(fn, interval = 1000, maxTimes = 10) {
2 let count = 0;
3
4 return new Promise((resolve, reject) => {
5 const execute = async () => {
6 try {
7 const result = await fn();
8 if (result.done) {
9 resolve(result);
10 } else if (count >= maxTimes) {
11 reject(new Error("poll timeout"));
12 } else {
13 count++;
14 setTimeout(execute, interval);
15 }
16 } catch (e) {
17 reject(e);
18 }
19 };
20
21 execute();
22 });
23}
答题时可以补充:
- 根据业务需求,可以把
result.done换成任意条件; - 如果需要长时间轮询,应该注意取消/清理机制(例如返回一个可以停止轮询的函数)。
这几道题不要求“一字不差记住代码”,面试官更期待的是你能先用自然语言描述思路,再写出结构清晰、易于沟通的实现。