Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Sharding): Update Shard#broadcastEval to v13 syntax. #695

Merged
merged 15 commits into from
Jul 24, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 10 additions & 7 deletions guide/sharding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const { Client } = require('discord.js');
const client = new Client();
const prefix = '!';

client.on('message', message => {
client.on('messageCreate', message => {
if (!message.content.startsWith(prefix) || message.author.bot) return;

const args = message.content.slice(prefix.length).trim().split(/ +/);
Expand All @@ -67,7 +67,7 @@ Let's say your bot is in a total of 3,600 guilds. Using the recommended shard co

## FetchClientValues

<DocsLink path="class/ShardClientUtil?scrollTo=fetchClientValues">Shard#fetchClientValues</DocsLink> is one of the most common sharding utility methods you'll be using. This method retrieves a property on the Client object of all shards.
One of the most common sharding utility methods you'll be using is <DocsLink path="class/ShardClientUtil?scrollTo=fetchClientValues">Shard#fetchClientValues</DocsLink>. This method retrieves a property on the Client object of all shards.

Take the following snippet of code:

Expand Down Expand Up @@ -108,16 +108,19 @@ client.on('message', message => {

## BroadcastEval

Next, check out another handy sharding method known as <DocsLink path="class/ShardClientUtil?scrollTo=broadcastEval">`Shard#broadcastEval`</DocsLink>. This method makes all of the shards evaluate a given script, where `this` is the `client` once each shard gets to evaluating it. You can read more about the `this` keyword [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this). For now, essentially understand that it is the shard's Client object.
Next, check out another handy sharding method known as <DocsLink path="class/ShardClientUtil?scrollTo=broadcastEval">`Shard#broadcastEval`</DocsLink>. This method makes all of the shards evaluate a given method, which recieves a `client` and a `context` argument. The `client` argument refers to the Client object of the shard evaluating it. You can read about the `context` argument [here](/sharding/additional-information.md#eval-arguments).

```js
client.shard.broadcastEval('this.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)').then(console.log);
client.shard
.broadcastEval(client => client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0))
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
.then(console.log);
```

This will run the code given to `broadcastEval` on each shard and return the results to the Promise as an array, once again. You should see something like `[9001, 16658, 13337, 15687]` logged. The code sent to each shard adds up the `memberCount` property of every guild that shard is handling and returns it, so each shard's total guild member count. Of course, if you want to total up the member count of *every* shard, you can do the same thing again on the Promise results.

```js
client.shard.broadcastEval('this.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)')
client.shard
.broadcastEval(client => client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0))
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
.then(results => {
return message.channel.send(`Total member count: ${results.reduce((acc, memberCount) => acc + memberCount, 0)}`);
})
Expand All @@ -131,7 +134,7 @@ You'd likely want to output both pieces of information in the stats command. You
```js
const promises = [
client.shard.fetchClientValues('guilds.cache.size'),
client.shard.broadcastEval('this.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)'),
client.shard.broadcastEval(client => client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)),
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
];

Promise.all(promises)
Expand All @@ -151,7 +154,7 @@ client.on('message', message => {
if (command === 'stats') {
const promises = [
client.shard.fetchClientValues('guilds.cache.size'),
client.shard.broadcastEval('this.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)'),
client.shard.broadcastEval(client => client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)),
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
];

return Promise.all(promises)
Expand Down
61 changes: 18 additions & 43 deletions guide/sharding/additional-information.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ For shards to communicate, they have to send messages to one another, as they ea
manager.spawn()
.then(shards => {
shards.forEach(shard => {
shard.on('message', message => {
shard.on('messageCreate', message => {
console.log(`Shard[${shard.id}] : ${message._eval} : ${message._result}`);
});
});
Expand All @@ -36,11 +36,13 @@ You can also send messages via `process.send('hello')`, which would not contain
There might be times where you want to target a specific shard. An example would be to kill a specific shard that isn't working as intended. You can achieve this by taking the following snippet (in a command, preferably):

::: tip
In discord.js v12, <DocsLink path="class/ShardClientUtil?scrollTo=ids">`client.shard`</DocsLink> can hold multiple ids. If you use the default sharding manager, the `.ids` array will only have one entry.
In discord.js v13, <DocsLink path="class/ShardClientUtil?scrollTo=ids">`client.shard`</DocsLink> can hold multiple ids. If you use the default sharding manager, the `.ids` array will only have one entry.
:::

```js
client.shard.broadcastEval('if (this.shard.ids.includes(0)) process.exit();');
client.shard.broadcastEval(client => {
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
if (client.shard.ids.includes(0)) process.exit();
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
});
```

If you're using something like [PM2](http://pm2.keymetrics.io/) or [Forever](https://github.com/foreverjs/forever), this is an easy way to restart a specific shard. Remember, <DocsLink path="class/ShardClientUtil?scrollTo=broadcastEval">Shard#broadcastEval</DocsLink> sends a message to **all** shards, so you have to check if it's on the shard you want.
Expand Down Expand Up @@ -75,50 +77,23 @@ You can access them later as usual via `process.argv`, which contains an array o

## Eval arguments

There may come the point where you will want to pass functions or arguments from the outer scope into a `.broadcastEval()` call.
There may come the point where you will want to pass arguments from the outer scope into a `.broadcastEval()` call.

```js
client.shard.broadcastEval(`(${funcName})('${arg}')`);
```

In this small snippet, an entire function is passed to the eval. It needs to be encased in parenthesis; it will throw errors on its way there otherwise. Another set of parenthesis is required so that the function gets called. Finally, the passing of the argument itself, which slightly varies, depending on the type of argument you are passing. If it's a string, you must wrap it in quotes, or it will be interpreted as is and will throw a syntax error because it won't be a string by the time it gets there.

Now, what if you wanted to call a function from *within* the client context? That is to say, you are not passing a function. It would look something like this:

```js
client.shard.broadcastEval(`this.${funcName}(${args});`);
```
function funcName(client, { arg }) {
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
// ...
}

This would become `client.funcName(args)` once it gets through. This is handy if you, for example, have extended your client object with your class and wish to call some of its methods manually.

### Asynchronous functions

There may be a time when you want to have your shard process an asynchronous function. Here's how you can do that!

```js
client.shard.broadcastEval(`
let channel = this.channels.cache.get('id');
let msg;
if (channel) {
msg = channel.messages.fetch('id').then(m => m.id);
}
msg;
`);
client.shard.broadcastEval(funcName, { context: { arg: 'arg' } });
```

This snippet allows you to return fetched messages outside of the `broadcastEval`, letting you know whether or not you were able to retrieve a message, for example. Remember, you aren't able to return entire objects outside. Now, what if we wanted to use `async/await` syntax inside?
The <DocsLink path="typedef/BroadcastEvalOptions">`BroadcastEvalOptions`</DocsLink> typedef was introduced in discord.js v13 as the second parameter in `.broadcastEval()`.
It accepts two properties: `shard` and `context`. The `context` property will be sent as the second argument to your function.

```js
client.shard.broadcastEval(`
(async () => {
let channel = this.channels.cache.get('id');
let msg;
if (channel) {
msg = await channel.messages.fetch('id').then(m => m.id);
}
return msg;
})();
`);
```
In this small snippet, an argument is passed to the `funcName` function through this parameter.
The function will recieve the arguments as an object as the second parameter.

This example will work the same, but you can produce cleaner code with `async/await`. Additionally, what this does is declare an asynchronous function and then immediately call it. As it is also the last declared line, it is effectively being returned. Remember that you need to `return` an item inside a function one way or another.
::: warning
The `context` option only accepts properties which are JSON-serializable.
This means you cannot pass complex data types in the context directly.
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
:::
49 changes: 22 additions & 27 deletions guide/sharding/extended.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ This page is a follow-up and bases its code on [the previous page](/sharding/add

## Sending messages across shards

Let's start with the basic usage of shards. At some point in bot development, you might have wanted to send a message to another channel, which may or may not necessarily be on the same guild, which means it may or may not be on the same shard. To achieve this, you will need to go back to your friend `.broadcastEval()` and try every shard for the desired channel. Suppose you have the following code in your `message` event:
Let's start with the basic usage of shards. At some point in bot development, you might have wanted to send a message to another channel, which may or may not necessarily be on the same guild, which means it may or may not be on the same shard. To achieve this, you will need to go back to your friend `.broadcastEval()` and try every shard for the desired channel. Suppose you have the following code in your `messageCreate` event:

```js {3-11}
client.on('message', message => {
client.on('messageCreate', message => {
// ...
if (command === 'send') {
if (!args.length) return message.reply('please specify a destination channel id.');
Expand All @@ -26,32 +26,31 @@ client.on('message', message => {
This will never work for a channel that lies on another shard. So, let's remedy this.

::: tip
In discord.js v12, <DocsLink path="class/ShardClientUtil?scrollTo=ids">`client.shard`</DocsLink> can hold multiple ids. If you use the default sharding manager, the `.ids` array will only have one entry.
In discord.js v13, <DocsLink path="class/ShardClientUtil?scrollTo=ids">`client.shard`</DocsLink> can hold multiple ids. If you use the default sharding manager, the `.ids` array will only have one entry.
:::

```js {4-13}
if (command === 'send') {
if (!args.length) return message.reply('please specify a destination channel id.');

return client.shard.broadcastEval(`
const channel = this.channels.cache.get('${args[0]}');
return client.shard.broadcastEval(async (client, { channelId }) => {
const channel = client.channels.cache.get(channelId);
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
if (channel) {
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
channel.send('This is a message from shard ${this.shard.ids.join(',')}!');
true;
} else {
false;
await channel.send(`This is a message from shard ${client.shard.ids.join(',')}!`);
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
`)
return false;
}, { context: { channelId: args[0] } })
.then(console.log);
}
```

If all is well, you should notice an output like `[false, true, false, false]`. If it is not clear why `true` and `false` are hanging around, the last expression of the eval statement will be returned. You will want this if you want any feedback from the results. Now that you have observed said results, you can adjust the command to give yourself proper feedback, like so:

```js {4-10}
return client.shard.broadcastEval(`
return client.shard.broadcastEval(client => {
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
// ...
`)
})
.then(sentArray => {
// Search for a non falsy value before providing feedback
if (!sentArray.includes(true)) {
Expand All @@ -65,10 +64,10 @@ And that's it for this section! You have successfully communicated across all of

## Using functions continued

If you remember, there was a brief mention of passing functions through `.broadcastEval()`, but no super clear description of exactly how to go about it. Well, fret not, for this section will cover it! Suppose you have the following code in your `message` event:
If you remember, there was a brief mention of passing functions through `.broadcastEval()`, but no super clear description of exactly how to go about it. Well, fret not, for this section will cover it! Suppose you have the following code in your `messageCreate` event:

```js {3-8}
client.on('message', message => {
client.on('messageCreate', message => {
// ...
if (command === 'emoji') {
if (!args.length) return message.reply('please specify an emoji id to search for.');
Expand All @@ -84,29 +83,25 @@ The aforementioned code will essentially search through `client.emojis.cache` fo
Let's start with a basic function, which will try to grab an emoji from the current client and return it.

```js
function findEmoji(nameOrID) {
return this.emojis.cache.get(nameOrID) || this.emojis.cache.find(e => e.name.toLowerCase() === nameOrID.toLowerCase());
function findEmoji(client, { nameOrId }) {
return client.emojis.cache.get(nameOrId) || client.emojis.cache.find(e => e.name.toLowerCase() === nameOrId.toLowerCase());
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
}
```

Next, you need to call the function in your command properly. If you recall from [this section](/sharding/additional-information.md#eval-arguments), it is shown there how to pass a function and arguments correctly. `.call()` will also be used to preserve the `client` context in the function that passes through.
Next, you need to call the function in your command properly. If you recall from [this section](/sharding/additional-information.md#eval-arguments), it is shown there how to pass a function and arguments correctly.

```js {4-7}
client.on('message', message => {
client.on('messageCreate', message => {
// ...
if (command === 'emoji') {
if (!args.length) return message.reply('please specify an emoji id to search for.');

return client.shard.broadcastEval(`(${findEmoji}).call(this, '${args[0]}')`)
return client.shard.broadcastEval(findEmoji, { context: { nameOrId: args[0] } })
.then(console.log);
}
});
```

::: tip
If you are unsure as to what `.call()` does, you may read up on it [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call).
:::

Now, run this code, and you will surely get a result that looks like the following:

<!-- eslint-skip -->
Expand All @@ -133,11 +128,11 @@ Now, run this code, and you will surely get a result that looks like the followi
]
```

While this result isn't *necessarily* bad or incorrect, it's simply a raw object that got `JSON.parse()`'d and `JSON.stringify()`'d over, so all of the circular references are gone. More importantly, The object is no longer a true `GuildEmoji` object as provided by discord.js. *This means none of the convenience methods usually provided to you are available.* If this is a problem for you, you will want to handle the item *inside* the `broadcastEval`. Conveniently, the `findEmoji` function will be ran inside it, so you should execute your relevant methods there, before the object leaves the context.
While this result isn't *necessarily* bad or incorrect, it's simply a raw object that got `JSON.parse()`'d and `JSON.stringify()`'d over, so all of the circular references are gone. More importantly, The object is no longer a true `GuildEmoji` object as provided by discord.js. *This means none of the convenience methods usually provided to you are available.* If this is a problem for you, you will want to handle the item *inside* the `broadcastEval`. Conveniently, the `findEmoji` function will be run, so you should execute your relevant methods there, before the object leaves the context.

```js {2-3,5-6}
function findEmoji(nameOrID) {
const emoji = this.emojis.cache.get(nameOrID) || this.emojis.cache.find(e => e.name.toLowerCase() === nameOrID.toLowerCase());
function findEmoji(client, { nameOrId }) {
const emoji = client.emojis.cache.get(nameOrId) || client.emojis.cache.find(e => e.name.toLowerCase() === nameOrId.toLowerCase());
WiseDevHelper marked this conversation as resolved.
Show resolved Hide resolved
if (!emoji) return null;
// If you wanted to delete the emoji with discord.js, this is where you would do it. Otherwise, don't include this code.
emoji.delete();
Expand All @@ -148,7 +143,7 @@ function findEmoji(nameOrID) {
With all that said and done, usually you'll want to display the result, so here is how you can go about doing that:

```js {2-7}
return client.shard.broadcastEval(`(${findEmoji}).call(this, '${args[0]}')`)
return client.shard.broadcastEval(findEmoji, { context: { nameOrId: args[0] } })
.then(emojiArray => {
// Locate a non falsy result, which will be the emoji in question
const foundEmoji = emojiArray.find(emoji => emoji);
Expand Down