You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

setInterval定时请求延迟:未完成请求状态与丢帧实现方案

高频HTTP请求更新Canvas的问题与解决方案

需求背景

需要每秒10次通过HTTP请求获取数据,更新<canvas>中的图像,原实现代码如下:

var i = 0;
setInterval(() => {
    i += 1;
    console.log("sending request", i);
    fetch("/data")   // 该请求耗时有时会超过100ms
        .then((r) => r.arrayBuffer())
        .then((arr) => {
            // 通过putImageData更新canvas...
            console.log("finished processing", i);
        });

}, 100);

实际运行问题

由于请求耗时偶尔超过100ms,出现了如下日志现象:

sending request 1
sending request 2
sending request 3
finished processing 3
sending request 4
sending request 5
finished processing 5
sending request 6
sending request 7
sending request 8

技术问询与解答

1. 未完成的请求是会排队导致后续更慢,还是直接被丢弃?

未完成的请求不会被自动丢弃,浏览器会并行处理多个同源HTTP请求(一般Chrome限制为6个并发连接)。但如果请求持续堆积超过并发限制,后续新请求会进入等待队列,占用网络连接池资源,导致整体响应变慢;同时所有已发出的请求返回后,对应的回调都会执行,就像日志里看到的请求1、2先发出,但请求3先完成,说明三个请求是并行处理的,只是返回顺序不同。

2. 如何修改示例代码来验证上述两种情况?

验证「请求堆积导致变慢」

给每个请求添加时间戳和并发数统计,观察请求耗时和活跃请求数的变化:

let i = 0;
let activeRequests = 0;
setInterval(() => {
    i += 1;
    const sendTime = Date.now();
    console.log(`[${sendTime}] 发送请求 ${i}, 活跃请求数: ${++activeRequests}`);
    fetch("/data")
        .then((r) => r.arrayBuffer())
        .then((arr) => {
            const finishTime = Date.now();
            console.log(`[${finishTime}] 完成请求 ${i}, 耗时: ${finishTime - sendTime}ms, 活跃请求数: ${--activeRequests}`);
        })
        .catch(() => {
            --activeRequests;
        });
}, 100);

如果日志中活跃请求数持续增长,且后续请求的耗时越来越长,说明请求在堆积,导致后续请求等待空闲连接而变慢。

验证「请求是否被丢弃」

让服务器给每个请求返回唯一标识,前端对比发送的请求序号和返回的标识:
修改Python服务器代码:

import bottle, time
from itertools import count

app = bottle.Bottle()
req_counter = count(1)

@bottle.route('/')
def index():
    return open("test.html", "r").read()

@bottle.route('/data')
def data():
    time.sleep(0.150)
    req_id = next(req_counter)
    return f"{req_id}".encode()

bottle.run(port=80)

修改前端代码:

let i = 0;
setInterval(() => {
    i += 1;
    console.log("发送请求", i);
    fetch("/data")
        .then((r) => r.text())
        .then((reqId) => {
            console.log(`完成请求 ${reqId}, 发送时序号为 ${i}`);
        });
}, 100);

如果返回的reqId是连续的,说明所有发出的请求都得到了响应,没有被丢弃。

3. 针对视频场景,若服务器响应过慢需丢弃帧,该如何实现?

视频场景需要优先处理最新帧,可通过以下两种方式实现:

方式1:用AbortController取消旧请求

每次发起新请求前,取消上一个未完成的请求,节省网络资源:

let i = 0;
let abortController = null;

function fetchAndUpdate() {
    i += 1;
    // 取消上一个未完成的请求
    if (abortController) {
        abortController.abort();
    }
    abortController = new AbortController();
    console.log("发送请求", i);
    fetch("/data", { signal: abortController.signal })
        .then((r) => {
            if (r.ok) return r.arrayBuffer();
            throw new Error("请求失败");
        })
        .then((arr) => {
            console.log("完成请求", i);
            // 更新canvas...
        })
        .catch((err) => {
            if (err.name !== "AbortError") {
                console.error("请求错误:", err);
            }
        });
}

// 用setTimeout替代setInterval,避免请求堆积(可选)
function scheduleNext() {
    fetchAndUpdate();
    setTimeout(scheduleNext, 100);
}
scheduleNext();

方式2:标记最新请求,忽略旧响应

不取消请求,仅在回调中判断当前响应是否为最新帧:

let i = 0;
let latestRequestId = 0;

setInterval(() => {
    const currentId = ++i;
    latestRequestId = currentId;
    console.log("发送请求", currentId);
    fetch("/data")
        .then((r) => r.arrayBuffer())
        .then((arr) => {
            // 如果当前响应不是最新的,直接丢弃
            if (currentId !== latestRequestId) {
                console.log("丢弃旧帧", currentId);
                return;
            }
            console.log("完成请求", currentId);
            // 更新canvas...
        });
}, 100);

内容的提问来源于stack exchange,提问作者Basj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 20:32:42