0

I like to browse the Discord tag and solve questions where I can. I'm fairly fluent in Python but only passable in Javascript, but I do throw my hat in the ring sometimes.
The Discord.py library has many functions that must be awaited. Failing to do so will not run.
The Discord.js library returns many promises for different things, and as I was writing answers involving those it was natural for me to add await. I also looked up a tutorial to see best practice for writing async/await in Discord.js.
Further, adding await required me to make my event handlers async, irrationally reinforcing my belief these await statements needed to exist.

client.on('message', async message => {
   await message.member.edit({mute: true})
});

This all works well and good, and I foolishly began recommending others do the same. However, in reading questions and writing answers, I've stumbled on something befuddling to me.

client.on('message', message => {
        let memberArray = [message.member];
        memberArray.forEach(member => member.edit({mute: true}));
        console.log("Do something after promised mute");
});

This code, with no async function nor awaited promise, still runs. This seems weird to me (what is the point of awaiting if the code can be executed, seemingly in sync order, without it?).

Continuing the confuse streak, I can make this function async and still run it without awaiting the promise. The only thing that will cause an error is an await statement in a synchronous function.

The other possibility is that this is unsafe and going to cause problems in production, but since my test bot only interacts with one user (me) doing 1 thing at a time, I can't see the problem. Does Node.js just let me run bad code without warning me? Have I overthought/overengineered a non-problem? What's the deal with airline food?

Allister
  • 903
  • 1
  • 5
  • 15
  • 2
    That `console.log()` in the second snippet does not wait for the `member.edit()` promises to resolve. – Patrick Roberts Sep 26 '20 at 17:00
  • Waiting for a promise is optional. There are good reasons for this: one is that you may decide to fire and forget, another is that you may want to gather multiple promises and use promise.all on them. – Radu Diță Sep 26 '20 at 17:08

1 Answers1

5

There is no requirement in Javascript to await a promise. If you want to make an asynchronous call that returns a promise and ignore when it completes, you can do that just fine. That is sometimes referred to as "fire and forget". I often do this with closing files, for example since I have nothing useful to do when the file is finally closed.

There is a requirement to catch any rejection from a promise that can happen. So, if your promise has any chance of being rejected, you will need at least use a .catch() or a try/catch around an await.

In this particular example:

client.on('message', async message => {
   await message.member.edit({mute: true})
});

There is no requirement to use await there and, in fact, it does nothing useful at all. It doesn't stop or pause anything. If message.member.edit({mute: true}) returns a promise, then the await would suspend execution of that callback and immediately return a promise back to the event system that triggered the event, but other events in nodejs will continue to get processed and that's the last line in the function anyway, so nothing is actually accomplished with the await.

If message.member.edit({mute: true}) returns a promise and you want to wait for all of them to be done before you do something else in your callback, then you will have to either process them serially with a for loop or process them in parallel and track them with Promise.all():

// process serially with for loop and await  
client.on('message', async message => {
    try {
        let memberArray = [message.member];
        for (let member of memberArray) {
            await member.edit({mute: true});
        }
        console.log("Do something after promised mute is completed");
     } catch(e) {
        // catch any rejections
        console.log(e);
     }
});

// process in parallel with Promise.all()
client.on('message', async message => {
    try {
        let memberArray = [message.member];
        await Promise.all(memberArray.map(member => member.edit({mute: true})));
        console.log("Do something after promised mute is completed");
     } catch(e) {
        // catch any rejections
        console.log(e);
     }
});

Now, if member.edit({mute: true}) is not asynchronous and returning a promise linked to the asynchronous operation, then all of this is moot as no await is needed or useful in any circumstance.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Even when [firing promises without waiting one should not forget](https://stackoverflow.com/a/32385430/1048572) to handle rejections on them. – Bergi Sep 26 '20 at 19:25
  • @Bergi - Is that not clear from my second paragraph? – jfriend00 Sep 26 '20 at 19:28