1
    async function displayEmbed(args, msg) {
    var bridgeembed = await getBridgeClutcherStats(args, msg) //should set bridgeembed to an embed
    var omniembed = await getOmniClutcherStats(args, msg) //should set omniembed to an embed
    var extembed = await getExtClutcherStats(args, msg) //should set extembed to an embed

    var desccc = "```Bridge Clutch: ```\n" + "```Omni Clutch: ⚛```\n" + "```Extension Clutch: ```\n";
    new Menu(msg.channel, msg.author.id, [{
    name: "main",
    content: new MessageEmbed({
        title: "Please Choose a Gamemode",
        description: desccc,
        url: "https://3d4h.world",
        color: 16711935,
        author: {
            name: args[1],
            url: `https://namemc.com/profile/${args[1]}`,
            icon_url: `https://mc-heads.net/body/${args[1]}`
        }
    }),
    reactions: {
        "": "stop",
        "": "bridgeclutch",
        "⚛": "omniclutch",
        "": "extclutch"
    }
},
{
    name: "bridgeclutch",
    content: bridgeembed,
    reactions: {
        "◀": "main"
},
    name: "omniclutch",
    content: omniembed,
    reactions: {
        "◀": "main"
    },
    name: "extclutch",
    content: extembed,
    reactions: {
        "◀": "main"
    }

}
]);

}

So I've been trying to have three functions run before I create an embed menu. I followed these (How to synchronously call a set of functions in javascript) steps but bridgeembed, omniembed, and extembed still ended up as undefined.

When I directly sent the embed from the functions, it worked. I also tried using callbacks like this:

       getOmniClutcherStats(args, msg, function(omniclutchEmbed){

            getExtClutcherStats(args, msg, function(extclutchEmbed){

                getBridgeClutcherStats(args, msg, function(bridgeclutchEmbed) {

                    new Menu(msg.channel, msg.author.id, [{
                        name: "main",
                        content: new MessageEmbed({
                            title: "Please Choose a Gamemode",
                            description: desccc,
                            url: "https://3d4h.world",
                            color: 16711935,
                            author: {
                                name: args[1],
                                url: `https://namemc.com/profile/${args[1]}`,
                                icon_url: `https://mc-heads.net/body/${args[1]}`
                            }
                        }),
                        reactions: {
                            "": "stop",
                            "": "bridgeclutch",
                            "⚛": "omniclutch",
                            "": "extclutch"
                        }
                    },
                    {
                        name: "bridgeclutch",
                        content: bridgeembed,
                         reactions: {
                            "◀": "main"
                       },
                        name: "omniclutch",
                         content: omniembed,
                         reactions: {
                            "◀": "main"
                          },
                        name: "extclutch",
                         content: extembed,
                         reactions: {
                            "◀": "main"
                        }
                
                    }
                    ]);
                
                
                });
        

            });
        });

But only when getting bridgeembed it worked, for the others UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'content' of undefined

How can I save the returned or callbacked embeds to variables so I can access them in the reaction menu?

Thanks in advance!

  • 1
    Why are your functions nested? Where do you `return` any values from them? – O. Jones Sep 24 '20 at 00:35
  • @O.Jones I returned inside the functions (https://pastebin.com/Xzk4eQ9E this is one of them), I've nested the callbacks in the second attempt. Unless I'm missing something? – Bardia Anvari Sep 24 '20 at 00:48
  • For starters, `await` only does something useful when you await a promise. Seeing as none of the functions you're trying to use `await` on return promises, this is clearly wrong. – jfriend00 Sep 24 '20 at 01:14
  • 1
    @jfriend00 mostly right (particularly in this case). A more accurate mental model is that `await` returns exactly what the function returns _except_ if the return type is a `Promise`, in which case it yields execution until the promise resolves. – msbit Sep 24 '20 at 01:48
  • @msbit - Which means it is pointless unless it's awaiting a promise. A lot of people start with the notion that `await` somehow has super powers to know when things in a function are done. It does not have any such powers. All it does is operate on a promise that the function returns. If there's no promise, it does nothing useful - as it just passes a standard return value through unchanged. This is what a lot of people (including the OP) need to learn. – jfriend00 Sep 24 '20 at 02:16
  • @jfriend00 agreed about the absence of super powers :) I'd say it's not quite pointless, particularly in situations where a function can return either a regular value or a promise, for example a cache/memoised function wrapping an asynchronous resource. In that scenario, use of `await` allows the caller to not care about that detail; syntactically it's the same. – msbit Sep 24 '20 at 02:35
  • @msbit - Yes, if it can return a promise, it's useful. But, in the OP's case, not useful, just misleading. – jfriend00 Sep 24 '20 at 02:58
  • Learning this Promise / async / await business is the hardest part of transitioning from other programming languages to Javascript. With respect, dear questioner, you have not learned it yet. Simplify your problem and keep trying. Maybe develop just one of your three functions? – O. Jones Sep 24 '20 at 09:45

1 Answers1

2

As mentioned by @o-jones and @jfriend00 you'll need to return something directly from your functions in order to have them available for use in displayEmbed. Further, as mentioned by @jfriend00 if you want to use async/await to ensure the calls happen in sequence, you'll need to ensure that the return type of the functions is a Promise that will resolve with the result, or the result itself if that can be determined synchronously.

Looking over the provided code for getExtClutcherStats in that PasteBin, you are calling a few asynchronous functions:

  • connection.query
  • MojangAPI.nameToUuid
  • MojangAPI.profile
  • average

all using the NodeJS style callback passing pattern. Additionally, in your happy path, you are returning extclutchembed nested inside all of these calls:

connection.query('SELECT * FROM C3', function (error, rows, fields) {
  …
  MojangAPI.nameToUuid(args[1], function (err, res) {
    …
    MojangAPI.profile(rows[playerNum].UUID, function (err, res) {
      …
      average(`/home/bardia/3d4hbot/${res.name}.png`, (err, color) => {
        …
        return extclutchEmbed
      }
      …
    }
    …
  }
  …
}

Given that structure, extclutchEmbed will only be returned to the immediate callback function (average in the happy path).

In this case, the quickest way to ensure that a Promise is returned would be to wrap the logic inside the function passed to the Promise constructor, and to pass the result you would like returned on promise resolution to the resolve function, like so:

function getExtClutcherStats (args, msg) {
  …
  return new Promise((resolve, reject) => {
    …
    connection.query('SELECT * FROM C3', function (error, rows, fields) {
      …
      MojangAPI.nameToUuid(args[1], function (err, res) {
        …
        MojangAPI.profile(rows[playerNum].UUID, function (err, res) {
          …
          average(`/home/bardia/3d4hbot/${res.name}.png`, (err, color) => {
            …
            resolve(extclutchEmbed)
            return
          }
          …
        }
        …
      }
      …
    }
  });
}

You'll need to work through the additional places that you return in that function to ensure that either the caller can handle them or, if you want to push the error handling onto the caller, you can use the reject function; for example, near the top:

if (args[1].length > 16) {
  msg.channel.send('Invalid Player Name')
  return
}

could become:

if (args[1].length > 16) {
  reject('Invalid Player Name')
  return
}

and the call site could then become:

try {
  var extembed = await getExtClutcherStats(args, msg)
} catch (error) {
  msg.channel.send(error);
}
msbit
  • 4,152
  • 2
  • 9
  • 22
  • for some reason I'm still having an issue. This: https://pastebin.com/xySa44Lx is my call site and This: https://pastebin.com/DRFsXK8p is another one of my functions that got the same treatment. The last function in the try/catch, getExtClutcherStats worked and set the variable to the embed, but the first and second functions in their try/catches both set the variable to undefined, other than a few small changes they are all the same and the Promise parts are exactly the same. Do you know what's going on @msbit? – Bardia Anvari Sep 24 '20 at 04:16
  • In `getBridgeClutcherStats` there are a number of ways the promise won't be resolved 1) `connection.query` encounters an error 2) `MojangAPI.nameToUuid` encounters an error 3) `foundPlayer` is false 4) `MojangAPI.profile` encounters an error. It's up to you how to deal with this; I'd suggest calling `reject` with the errors passed to their callbacks (similar to the `args[1].length` check). Additionally, in `displayEmbed` you'd be best served placing all the code that can throw, and all that is dependent on it, in the single `try` block. What is dependent is up to you. – msbit Sep 24 '20 at 05:21
  • Same issue. Function: https://pastebin.com/4c8aLXTz. Call Site: https://pastebin.com/cF1iL6G8. Although this: https://pastebin.com/WCpb6NNm function does change the variable to an embed. @msbit do you see a difference or another issue? – Bardia Anvari Sep 24 '20 at 06:12
  • In `displayEmbed`, I'd suggest moving the catch block down the bottom of the function, as you're using the `*embed` variables in the call to `new Menu`. Then, in that block, add a call to `console log` to also log out any errors, in addition to `msg.channel.send`. At this point, you should at least see any errors that are due to the rejected `Promise`. If you don't you'll need to think through the code paths in the `get*Stats` functions; perhaps sprinkling some `console.log`s to test your hypotheses about how the code is running. – msbit Sep 24 '20 at 11:38