Managing Internal Queues

Apparently Discord.js has an internal queueing system for managing API requests to prevent exceeding rate limits. When I try to handle rate limits explicitly using something like:
client.rest.on("rateLimited", (error) => {
// Handle rate limit
});
client.rest.on("rateLimited", (error) => {
// Handle rate limit
});
It doesn't seem to work as expected for changing a channel's name using channel.setName(). This appears to be because Discord.js manages rate-limiting internally and the queued calls for channel.setName() are sent sequentially once the rate limit expires from an internal timer. I wanted to manage the queuing myself to prevent all queued channel.setName() calls from being sent sequentially after the rate limit expires. Instead, I wanted to replace all pending (queued) calls with the latest requested name and make only one API call once the rate limit expires. So, I've decoupled the logic for changing a channel's name from Discord.js entirely. Instead of using channel.setName(), I implemented a custom queuing logic using direct HTTP PATCH requests to the Discord API... 1. Is this the best approach for handling rate limits in my case or is there a way to achieve the same behavior within Discord.js? 2. Can Discord.js's internal request queue be modified or extended to support replacing queued channel.setName() calls with only the latest value and discard previous ones? 3. Will combining my custom solution with Discord.js create issues? Versions Node v23.2.0 [email protected]
7 Replies
d.js toolkit
d.js toolkit3mo ago
- What's your exact discord.js npm list discord.js and node node -v version? - Not a discord.js issue? Check out #other-js-ts. - Consider reading #how-to-get-help to improve your question! - Explain what exactly your issue is. - Post the full error stack trace, not just the top part! - Show your code! - Issue solved? Press the button!
benluka.dev
benluka.dev3mo ago
You can probably set options in rest, it'll throw error instead of queueing. You could implement something there. Honestly if your solution is working perfectly, i would say stick with that. It won't mess with djs since its separate http request. Also think you can share your implementation for handling?
DD
DD3mo ago
1. Yes, rejectOnRateLimit 2. No 3. Mostly* no
c.ix
c.ixOP3mo ago
Sure, you can find it here. It does work independent from discord.js, and it worked as expected... 🤷‍♂️ How can I make changes to throw error instead of queueing ?
d.js docs
d.js docs3mo ago
:propertysignature: RESTOptions#rejectOnRateLimit @2.4.0 Determines how rate limiting and pre-emptive throttling should be handled. When an array of strings, each element is treated as a prefix for the request route (e.g. /channels to match any route starting with /channels such as /channels/:id/messages) for which to throw RateLimitErrors. All other request routes will be queued normally Default value: null
DD
DD3mo ago
yeah just use this option and implement your own re-queue in your catch
c.ix
c.ixOP3mo ago
great, cheers.
const log = require("./loggerUtils");
const { RateLimitError } = require('discord.js');

const channelNameUpdateQueue = new Map();

async function updateChannelName(channel, newName) {
const channelId = channel.id;

if (channelNameUpdateQueue.has(channelId)) {
const queue = channelNameUpdateQueue.get(channelId);
log.warn(`Rate limit pending for channel ${channelId}. Replacing queued name with "${newName}".`);
queue.latestName = newName;
return;
}

const queue = {
latestName: newName,
timeout: null,
};
channelNameUpdateQueue.set(channelId, queue);

log.debug(`Queuing name update for channel ${channelId}: "${newName}".`);

await processQueue(channelId, channel);
}

async function processQueue(channelId, channel) {
const queue = channelNameUpdateQueue.get(channelId);

if (!queue) return;

try {
await channel.setName(queue.latestName);
log.debug(`Channel name updated successfully: ${queue.latestName}`);

channelNameUpdateQueue.delete(channelId);
} catch (error) {
if (error instanceof RateLimitError) {
const retryAfter = error.retryAfter;
log.warn(`Rate limit hit for channel ${channelId}. Retrying after ${retryAfter}ms.`);
queue.timeout = setTimeout(() => {
processQueue(channelId, channel).catch((err) =>
log.error(`Failed to retry channel name update: ${err.message}`)
);
}, retryAfter);
} else {
log.error(`Error updating channel name: ${error.message}`);
channelNameUpdateQueue.delete(channelId);
}
}
}

module.exports = { updateChannelName };
const log = require("./loggerUtils");
const { RateLimitError } = require('discord.js');

const channelNameUpdateQueue = new Map();

async function updateChannelName(channel, newName) {
const channelId = channel.id;

if (channelNameUpdateQueue.has(channelId)) {
const queue = channelNameUpdateQueue.get(channelId);
log.warn(`Rate limit pending for channel ${channelId}. Replacing queued name with "${newName}".`);
queue.latestName = newName;
return;
}

const queue = {
latestName: newName,
timeout: null,
};
channelNameUpdateQueue.set(channelId, queue);

log.debug(`Queuing name update for channel ${channelId}: "${newName}".`);

await processQueue(channelId, channel);
}

async function processQueue(channelId, channel) {
const queue = channelNameUpdateQueue.get(channelId);

if (!queue) return;

try {
await channel.setName(queue.latestName);
log.debug(`Channel name updated successfully: ${queue.latestName}`);

channelNameUpdateQueue.delete(channelId);
} catch (error) {
if (error instanceof RateLimitError) {
const retryAfter = error.retryAfter;
log.warn(`Rate limit hit for channel ${channelId}. Retrying after ${retryAfter}ms.`);
queue.timeout = setTimeout(() => {
processQueue(channelId, channel).catch((err) =>
log.error(`Failed to retry channel name update: ${err.message}`)
);
}, retryAfter);
} else {
log.error(`Error updating channel name: ${error.message}`);
channelNameUpdateQueue.delete(channelId);
}
}
}

module.exports = { updateChannelName };
I now simply call await updateChannelName(channel, newName); instead of:
await channel.setName(``);
await channel.setName(``);
Here's how I added rejectOnRateLimit to the rest options in Client:
const client = new Client({
//intents: [...],
rest: {
rejectOnRateLimit: (info) => {
return info.route == "/channels/:id" && info.method == "PATCH";
},
},
});
const client = new Client({
//intents: [...],
rest: {
rejectOnRateLimit: (info) => {
return info.route == "/channels/:id" && info.method == "PATCH";
},
},
});
Ah, fixed it.

Did you find this page helpful?