3

My problem is that the code does not seem to be running in order, as seen below.

This code is for my discord.js bot that I am creating.



    var Discord = require("discord.js");
    var bot = new Discord.Client();
    var yt = require("C:/Users/username/Documents/Coding/Discord/youtubetest.js");
    var youtubetest = new yt();
    var fs = require('fs');
    var youtubedl = require('youtube-dl');
    var prefix = "!";
    var vidid;
    var commands = {
      play: {
       name: "!play ",
       fnc: "Gets a Youtube video matching given tags.",
       process: function(msg, query) {
         youtubetest.respond(query, msg);
         var vidid = youtubetest.vidid;
         console.log(typeof(vidid) + " + " + vidid);
         console.log("3");
       }
     }
    };

    bot.on('ready', () => {
      console.log('I am ready!');
    });

    bot.on("message", msg => {
      if(!msg.content.startsWith(prefix) || msg.author.bot || (msg.author.id === bot.user.id)) return;

      var cmdraw = msg.content.split(" ")[0].substring(1).toLowerCase();
      var query = msg.content.split("!")[1];
      var cmd = commands[cmdraw];
      if (cmd) {
        var res = cmd.process(msg, query, bot);
        if (res) {
          msg.channel.sendMessage(res);
        }
      } else {
        let msgs = [];
        msgs.push(msg.content + " is not a valid command.");
        msgs.push(" ");
        msgs.push("Available commands:");
        msgs.push(" ");
        msg.channel.sendMessage(msgs);
        msg.channel.sendMessage(commands.help.process(msg));
      }
    });

    bot.on('error', e => { console.error(e); });
    bot.login("mytoken");

The youtubetest.js file:



    var youtube_node = require('youtube-node');
    var ConfigFile = require("C:/Users/username/Documents/Coding/Discord/json_config.json");
    var mybot = require("C:/Users/username/Documents/Coding/Discord/mybot.js");

    function myyt () {
        this.youtube = new youtube_node();
        this.youtube.setKey(ConfigFile.youtube_api_key);
        this.vidid = "";
    }

    myyt.prototype.respond = function(query, msg) {
      this.youtube.search(query, 1, function(error, result) {
        if (error) {
          msg.channel.sendMessage("There was an error finding requested video.");
        } else {
          vidid = 'http://www.youtube.com/watch?v=' + result.items[0].id.videoId;
          myyt.vidid = vidid;
          console.log("1");
        }
      });
      console.log("2");
    };

    module.exports = myyt;

As the code shows, i have an object for the commands that the bot will be able to process, and I have a function to run said commands when a message is received. Throughout the code you can see that I have put three console.logs with 1, 2 and 3 showing in which order I expect the parts of the code to run. When the code is run and a query is found the output is this:



    I am ready!
    string + 
    2
    3
    1

This shows that the code is running in the wrong order that I expect it to.

All help is very highly appreciated :)

*Update! Thank you all very much to understand why it isn't working. I found a solution where in the main file at vidid = youtubetest.respond(query, msg) when it does that the variable is not assigned until the function is done so it goes onto the rest of my code without the variable. To fix I simply put an if statement checking if the variable if undefined and waiting until it is defined.*

  • You're writing async-code. The console.log 2 is outside an async function. I'm not sure what you'd expect to happen. – Erik Dec 08 '16 at 09:22
  • I'm sorry, I haven't done a lot of the kind of stuff, I'm a teenager and still learning Javascript. What exactly do you mean asynchronous, and what parts are and are not? – Nathan Bryan Dec 08 '16 at 09:23
  • `this.youtube.search` is probably an asynchronous function, meaning it performs something in the background while other code outside the *callback* of the function continues executing. So your `function(error, result) { ... }` is the callback which only executes *after* the search operation is complete. Thus the `console.log(2)` is naturally being called before the callback. – Fissio Dec 08 '16 at 09:27
  • But the `console.log(3)` in the first file is running first, so shouldn't the function `youtubetest.respond(query, msg)` run before the `console.log(3)`? – Nathan Bryan Dec 08 '16 at 09:33
  • 1
    `youtubetest.respond` is probably also asynchronous. – Fissio Dec 08 '16 at 10:10

2 Answers2

3

Like is mentioned before, a lot of stuff in javascript runs in async, hence the callback handlers. The reason it runs in async, is to avoid the rest of your code being "blocked" by remote calls. To avoid ending up in callback hell, most of us Javascript developers are moving more and more over to Promises. So your code could then look more like this:

   myyt.prototype.respond = function(query, msg) {
        return new Promise(function(resolve, reject) {
            this.youtube.search(query, 1, function(error, result) {
                if (error) {
                    reject("There was an error finding requested video."); // passed down to the ".catch" statement below

                } else {
                    vidid = 'http://www.youtube.com/watch?v=' + result.items[0].id.videoId;
                    myyt.vidid = vidid;
                    console.log("1");
                    resolve(2); // Resolve marks the promises as successfully completed, and passes along to the ".then" method
                }
            });
        }).then(function(two) {
            // video is now the same as myyt.vidid as above. 
            console.log(two);
        }).catch(function(err) {
            // err contains the error object from above
            msg.channel.sendMessage(err);
        })
    };

This would naturally require a change in anything that uses this process, but creating your own prototypes seems.. odd.

This promise returns the vidid, so you'd then set vidid = youtubetest.response(query, msg);, and whenever that function gets called, you do:

vidid.then(function(id) {
 // id is now the vidid.
});

Javascript runs async by design, and trying to hack your way around that leads you to dark places fast. As far as I can tell, you're also targetting nodeJS, which means that once you start running something synchronously, you'll kill off performance for other users, as everyone has to wait for that sync call to finish.

Some suggested reading:

I'd also suggest looking up ES6 syntax, as it shortens your code and makes life a hellofalot easier (native promises were only introduced in ES6, which NodeJS 4 and above supports (more or less))

Community
  • 1
  • 1
yusijs
  • 867
  • 9
  • 20
  • Thank you, this does help explaining my question, although i realised that the question was wrong but it still helps me out greatly. Thanks a lot! :) – Nathan Bryan Dec 08 '16 at 10:12
0

In javascript, please remember that any callback function you pass to some other function is called asynchronously. I.e. the calls to callback function may not happen "in order". "In order" in this case means the order they appear on the source file.

The callback function is simply called on certain event:

  • When there is data to be processed
  • on error
  • in your case for example when the youtube search results are ready, 'ready' event is received or 'message' is received.
  • etc.
teroi
  • 1,087
  • 10
  • 19
  • Ah ok, but how do I get the variable `vidid` back to mybot.js with the results of the youtube search. I should have probably specified better in the question. – Nathan Bryan Dec 08 '16 at 09:35
  • That's another question then. :-) In your play function, remove remove the 'var' from start of vidid variable then you won't override your vidid variable just a few rows above. – teroi Dec 08 '16 at 09:46
  • Then in elsewhere in code check if vidid !== undefined and use it. – teroi Dec 08 '16 at 09:51
  • I took your advice, yet it doesn't change the outcome. The output is still `string + ` – Nathan Bryan Dec 08 '16 at 10:11
  • this.vidid is still not changed from the initial value at that point as your console.log messages indicated. It is changed when you print 1. – teroi Dec 08 '16 at 10:59
  • 1
    Thanks @teroi. I updated my text above to show the way I fixed that. – Nathan Bryan Dec 08 '16 at 11:21