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

如何将Ember Data请求限流至每秒X次?

实现Ember Data请求限流至每秒X次的可行方案

我之前在几个Ember项目里处理过和REST API的速率限制兼容的需求,下面是我验证过的几种靠谱实现方式,核心思路是通过拦截Ember Data的请求流程,用队列或节流机制控制请求发送频率。

方案一:自定义适配器 + 请求队列服务(推荐)

这个方案能严格控制每秒发送X次请求,还能处理请求排队、错误重试(可选)和请求去重,非常适合需要精确速率控制的场景。

1. 创建请求限流服务

首先生成一个Ember Service来管理请求队列:

// app/services/request-throttle.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { config } from 'ember-get-config';

export default class RequestThrottleService extends Service {
  @tracked requestQueue = [];
  @tracked isProcessing = false;
  @tracked pendingRequests = new Map(); // 用于去重重复请求

  // 从项目配置读取每秒允许的请求数,默认2
  requestsPerSecond = config.requestThrottle?.requestsPerSecond || 2;
  // 计算每次请求的间隔时间
  get requestInterval() {
    return 1000 / this.requestsPerSecond;
  }

  /**
   * 加入请求到队列
   * @param {Function} requestFn - 包装原ajax请求的函数
   * @param {String} key - 请求唯一标识,用于去重
   * @param {Number} priority - 请求优先级,数值越高越先执行
   * @returns {Promise} 请求结果的Promise
   */
  enqueueRequest(requestFn, key, priority = 0) {
    // 去重:如果已有相同请求在队列或处理中,直接返回已有Promise
    if (this.pendingRequests.has(key)) {
      return this.pendingRequests.get(key);
    }

    const promise = new Promise((resolve, reject) => {
      this.requestQueue.push({ requestFn, resolve, reject, priority });
      // 按优先级排序队列,高优先级请求靠前
      this.requestQueue.sort((a, b) => b.priority - a.priority);
      this.processQueue();
    }).finally(() => {
      this.pendingRequests.delete(key);
    });

    this.pendingRequests.set(key, promise);
    return promise;
  }

  /**
   * 处理队列中的请求
   */
  async processQueue() {
    if (this.isProcessing || this.requestQueue.length === 0) return;

    this.isProcessing = true;

    while (this.requestQueue.length > 0) {
      const { requestFn, resolve, reject } = this.requestQueue.shift();
      try {
        // 执行原请求
        const result = await requestFn();
        resolve(result);
      } catch (error) {
        // 可以在这里添加重试逻辑,比如最多重试3次
        reject(error);
      }
      // 等待指定间隔后再处理下一个请求
      await new Promise(resolveTimer => setTimeout(resolveTimer, this.requestInterval));
    }

    this.isProcessing = false;
  }
}

2. 自定义REST适配器拦截请求

修改你的应用适配器,将所有Ember Data的ajax请求交给限流服务处理:

// app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';
import { inject as service } from '@ember/service';

export default class ApplicationAdapter extends RESTAdapter {
  @service requestThrottle;

  /**
   * 重写ajax方法,加入限流逻辑
   */
  async ajax(url, method, options) {
    // 生成请求唯一标识,用于去重
    const requestKey = `${method}:${url}:${JSON.stringify(options?.data || {})}`;
    // 读取请求优先级(可通过adapterOptions传递)
    const priority = options?.adapterOptions?.priority || 0;

    return this.requestThrottle.enqueueRequest(() => {
      // 调用Ember Data原生的ajax方法
      return super.ajax(url, method, options);
    }, requestKey, priority);
  }
}

3. 配置速率参数

在config/environment.js里添加限流配置(可选):

module.exports = function(environment) {
  let ENV = {
    // ...其他配置
    requestThrottle: {
      requestsPerSecond: 3 // 每秒允许3次请求
    }
  };
  // ...
  return ENV;
};

方案二:节流函数包装适配器请求(简易版)

如果你的需求不需要严格的队列控制,只是粗略限制请求频率,可以用节流函数包装适配器的ajax方法。注意:这种方式无法保证请求按顺序执行,也可能在异步场景下突破速率限制,适合对速率要求不高的场景。

// app/adapters/application.js
import RESTAdapter from '@ember-data/adapter/rest';

// 自定义节流函数,处理异步请求
function throttleAsync(func, delay) {
  let lastCallTime = 0;
  let pendingPromise = null;

  return async function(...args) {
    const now = Date.now();
    if (now - lastCallTime >= delay) {
      lastCallTime = now;
      pendingPromise = func.apply(this, args);
      return pendingPromise;
    } else {
      // 如果在节流间隔内,返回上一次的请求结果(或等待)
      return pendingPromise;
    }
  };
}

export default class ApplicationAdapter extends RESTAdapter {
  constructor() {
    super(...arguments);
    // 包装ajax方法,限制每秒2次请求
    this.ajax = throttleAsync(this.ajax.bind(this), 500);
  }
}

关键注意事项

  • 错误处理:如果某个请求失败,队列服务会自动继续处理下一个请求,不会阻塞整个队列;如果需要重试,可以在processQueue的catch块中添加重试逻辑。
  • 请求去重:队列服务中的pendingRequestsMap可以避免重复的请求被多次发送,比如用户快速点击触发的相同查询。
  • 优先级控制:通过adapterOptions可以给特定请求设置高优先级,比如用户的即时操作请求优先于后台同步请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:03:02