通过维护一个按优先级排序的请求队列,并在空闲时依次执行,可以实现 fetch api 的请求优先级控制。具体做法是定义一个包含优先级、请求配置和回调的队列结构,使用一个标志位控制并发执行,当低优先级请求完成或取消后,自动从队列头部取出高优先级请求继续发送。

如何设计基于优先级的请求调度器
调度器的核心在于管理待处理请求的顺序和执行状态。我们需要一个数组来存储请求对象,每个对象包含优先级数值、Fetch 参数以及成功或失败的回调。执行逻辑采用循环检查模式:只要队列不为空且当前没有正在进行的请求,就取出优先级最高(数值最小)的请求发起调用。调用结束后,无论成功与否,都更新状态并触发下一次检查,从而形成流水线式的执行流。

class PriorityScheduler {
constructor(maxConcurrent = 1) {
this.queue = [];
this.maxConcurrent = maxConcurrent;
this.activeCount = 0;
}
addRequest(priority, options, callback) {
const request = { priority, options, callback };
this.queue.push(request);
// 按优先级升序排列,数值越小优先级越高
this.queue.sort((a, b) => a.priority - b.priority);
this.processQueue();
}
processQueue() {
while (this.activeCount < this.maxConcurrent && this.queue.length > 0) {
const request = this.queue.shift();
this.activeCount++;
fetch(request.options.url, request.options)
.then(response => response.json())
.then(data => request.callback(null, data))
.catch(error => request.callback(error, null))
.finally(() => {
this.activeCount--;
this.processQueue();
});
}
}
}
// 使用示例
const scheduler = new PriorityScheduler(1);
scheduler.addRequest(1, { url: '/api/high' }, (err, data) => console.log('High', data));
scheduler.addRequest(3, { url: '/api/low' }, (err, data) => console.log('Low', data));
scheduler.addRequest(2, { url: '/api/medium' }, (err, data) => console.log('Medium', data));上述代码定义了一个简单的单并发调度器。当添加请求时,它会自动对队列进行排序。由于设置了最大并发数为 1,低优先级的请求必须等待高优先级请求完成后才能开始。这种机制确保了关键数据加载优先于非关键数据。
立即学习“Java免费学习笔记(深入)”;
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
如何处理请求取消与冲突
在实际应用中,用户操作可能导致之前的请求变得过时,因此需要支持取消机制。利用 AbortController 可以中断正在进行的 Fetch 请求。当新的高优先级请求加入时,如果存在相同资源或更低优先级的活跃请求,可以强制取消它们以释放资源。这需要修改调度器以追踪活跃的 AbortController,并在出队时检查是否需要 abort。

class CancelableScheduler extends PriorityScheduler {
constructor(maxConcurrent = 1) {
super(maxConcurrent);
this.activeControllers = new Map();
}
addRequest(priority, options, callback) {
const controller = new AbortController();
const signal = controller.signal;
// 合并信号,确保外部取消也能生效
const originalSignal = options.signal;
if (originalSignal) {
originalSignal.addEventListener('abort', () => controller.abort(), { once: true });
}
// 取消所有优先级低于当前请求的活跃请求
for (const [reqId, activeController] of this.activeControllers.entries()) {
const req = this.queue.find(r => r.controller === activeController);
if (req && req.priority > priority) {
activeController.abort();
this.activeControllers.delete(reqId);
}
}
const request = {
priority,
options: { ...options, signal },
callback,
controller
};
this.queue.push(request);
this.queue.sort((a, b) => a.priority - b.priority);
this.processQueue();
}
processQueue() {
while (this.activeCount < this.maxConcurrent && this.queue.length > 0) {
const request = this.queue.shift();
this.activeCount++;
this.activeControllers.set(request, request.controller);
fetch(request.options.url, request.options)
.then(response => response.json())
.then(data => request.callback(null, data))
.catch(error => {
if (error.name !== 'AbortError') {
request.callback(error, null);
}
})
.finally(() => {
this.activeCount--;
this.activeControllers.delete(request);
this.processQueue();
});
}
}
}扩展后的调度器引入了 AbortController 的管理。在添加新请求时,它会遍历当前队列和活跃请求,终止那些优先级低于新请求的任务。这避免了网络带宽被低优先级任务占用,同时通过捕获 AbortError 防止错误回调被误触发。这种策略在处理动态内容加载时非常有效,例如用户快速切换标签页时,只保留当前视图所需的高优先级数据请求。
当队列中存在多个相同优先级的请求时执行顺序如何确定
当多个请求具有相同的优先级数值时,JavaScript 的 Array.prototype.sort 方法通常是不稳定的,但在大多数现代引擎中,对于简单数值排序往往保持插入顺序。然而,为了确保确定性,应在排序算法中加入次级比较条件,即当优先级相同时,按照请求加入队列的时间戳或索引进行排序。这样可以保证先提交的同优先级请求先被执行,符合 FIFO(先进先出)的预期行为。
// 在 sort 函数中增加稳定性保障
this.queue.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
// 优先级相同时,按添加顺序(假设添加了 id 字段)排序
return a.id - b.id;
});通过在排序逻辑中引入 ID 比较,即使优先级相同,请求也会严格按照添加顺序执行。这对于需要保证数据一致性或依赖关系的同级请求尤为重要。例如,加载页面布局和资源时,虽然它们可能都被标记为高优先级,但样式表的加载应优先于图片资源,通过微调优先级数值或 ID 即可精确控制这一顺序。

















