并行请求合并

Sunday, April 2, 2023

并行请求合并

埋点通常在用户进入页面后会触发多个请求,例如页面浏览量(PV)、各个模块元素的曝光情况,以及接口请求的上报。这种情况可能导致在进行某个操作时,页面同时发送许多重复的请求。为了解决这个问题,与后端讨论后,决定支持将埋点请求合并,即允许接口以数组形式一次性上报多个埋点数据。

因此,前端需要实现一个自动合并请求的函数,该函数能够在一个指定的时间窗口内收集相同类型的请求,并在窗口结束时将它们合并为一个请求一起发送。这样不仅可以减少请求数量,还能提高性能和数据处理的效率。

// 假设这是一个发送埋点数据的函数
async function sendTrackingData(params) {
    // 模拟发送请求
    console.log('Sending tracking data:', params);
    return Promise.resolve(params); // 返回 Promise
}

// 创建自动合并请求的函数
const autoMergedRequest = createAutoMergedRequest(sendTrackingData, 200);

// 使用示例
function trackPageView() {
    autoMergedRequest({ type: 'page_view', page: 'home' });
}

function trackElementExposure(elementId) {
    autoMergedRequest({ type: 'element_exposure', elementId });
}

// 模拟用户行为
trackPageView(); // 记录页面浏览量
trackElementExposure('banner'); // 记录元素曝光
trackElementExposure('sidebar'); // 记录另一个元素曝光
trackElementExposure('footer'); // 记录另一个元素曝光

// 这里可以继续调用 autoMergedRequest 进行其他埋点
function createAutoMergedRequest(fn, windowMs = 200) {
    let queue = [];
    let timer = null;

    async function submitQueue() {
        timer = null; // 清空计时器以接受后续请求
        const _queue = [...queue];
        queue = []; // 清空队列

        try {
            const list = await fn(_queue.map((q) => q.params));
            _queue.forEach((q1, i) => {
                q1.resolve(list[i]);
            });
        } catch (err) {
            _queue.forEach((q2) => {
                q2.reject(err);
            });
        }
    }

    return (params) => {
        if (!timer) {
            // 如果没有开始窗口期,则创建
            timer = setTimeout(() => {
                submitQueue();
            }, windowMs);
        }

        return new Promise((resolve, reject) => {
            queue.push({
                params,
                resolve,
                reject,
            });
        });
    };
}