跳到內容

速率限制

為確保服務穩定,API 有請求頻率限制。

每個 API 回應都包含速率限制資訊:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1705312860
標頭說明
X-RateLimit-Limit每分鐘請求上限
X-RateLimit-Remaining剩餘可用次數
X-RateLimit-Reset重置時間(Unix timestamp)

當超過速率限制時,API 回傳:

HTTP/1.1 429 Too Many Requests
{
"error": "Rate limit exceeded",
"retryAfter": 45
}

retryAfter 表示需要等待的秒數。

async function apiRequest(url) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` }
});
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining && parseInt(remaining) < 10) {
console.warn(`Rate limit warning: ${remaining} requests remaining`);
}
return response;
}
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${API_KEY}` }
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
const delay = Math.min(
parseInt(retryAfter) * 1000,
Math.pow(2, attempt) * 1000
);
console.log(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
class RateLimitedQueue {
constructor(maxPerMinute = 60) {
this.queue = [];
this.processing = false;
this.interval = 60000 / maxPerMinute;
}
async add(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
setTimeout(() => {
this.processing = false;
this.process();
}, this.interval);
}
}
// 使用
const queue = new RateLimitedQueue(60);
async function fetchChannel(id) {
return queue.add(() =>
fetch(`/api/channels/${id}`, {
headers: { Authorization: `Bearer ${API_KEY}` }
})
);
}
// 不好:每筆資料一個請求
for (const id of channelIds) {
await fetch(`/api/channels/${id}`);
}
// 好:使用列表 API
const response = await fetch('/api/channels');
const { channels } = await response.json();
const cache = new Map();
async function getChannel(id) {
if (cache.has(id)) {
const { data, timestamp } = cache.get(id);
if (Date.now() - timestamp < 60000) {
return data;
}
}
const response = await fetch(`/api/channels/${id}`);
const data = await response.json();
cache.set(id, { data, timestamp: Date.now() });
return data;
}

對於即時資料,使用 Webhook 而非輪詢:

// 不好:每秒輪詢
setInterval(async () => {
await fetch('/api/channels/C123/backup/messages');
}, 1000);
// 好:使用 Webhook 接收事件
app.post('/webhook', (req, res) => {
handleNewMessage(req.body);
res.status(200).send('OK');
});