0

I'm building an Discord music bot and I need to generate one object with this function. Problem is that the function returns too early and the object is not build entirely. Loop in function only finishes after function is exited.

Function fetchVideoInfo() executes passed callback in .then()function of promise that it retuens, as provided on image. However i can't edit it because it's a part of a module. What I think that is a problem is that even tho i awaiting for fetchVideoInfo() to complete,it still goes on because of how it is written, executing callback after internal promise. Ill provide the part of it's code that returns the actual promise and calls the callback function. What I've tried is to have my function return promise but it did not work for what I think is the same issue, function ends before the callback, that i have to await. I've also tried wrapping callback function in another one and then passing it, while awaiting initial function, but it also didn't do well.

https://i.sstatic.net/6Qsm7.jpg This is link to image of the return value in that module("youtube-info"). The whole module is actually the fetchVideoInfo() function

async function generatePlayList(queue) {
  const date = new Date();
  let embed = new Discord.RichEmbed();
  embed
    .setTitle("Playlist")
    .setColor("#25473A")
    .setDescription("Music currently in playlist!")
    .setFooter("Time ")
    .setTimestamp(date);
  for await (let id of queue)
    fetchVideoInfo(id, (Null, info) => {
      const { duration, title, url } = info;
      const seconds = duration % 60;
      const minutes = Math.trunc(duration / 60);
      embed.addField(`[${title}](${url})`, `Duration ${minutes}:${seconds}`);
      console.log(embed.fields);
    });

  console.log(embed.fields);
  return embed;
}

What happens now is that the function returns first resulting in embed object unmodified, even though there is for - await - of loop(It is new ES2018 syntax) before it. It should first complete the for - of loop and then return

2
  • The await only takes effect on promises, even if the function is async if the method to wait is not a promise, it will not have the wait effect. In short you must transform your method that fills the object into a promise. Commented Jan 23, 2019 at 18:01
  • can you provide any example? I have tried in multiple ways but it didn't work as i expected Commented Jan 23, 2019 at 18:05

2 Answers 2

0

Await does not work with callbacks you have to change your function and should not return callback instead return a value and use it and on error throw error and catch it. for of runs asynchronous so it is your function that needs await.

async function generatePlayList(queue) {
  const date = new Date();
  let embed = new Discord.RichEmbed();
  embed
    .setTitle("Playlist")
    .setColor("#25473A")
    .setDescription("Music currently in playlist!")
    .setFooter("Time ")
    .setTimestamp(date);
  for(let id of queue)
   {
    try{
    const info = await fetchVideoInfo(id);
    const { duration, title, url } = info;
    const seconds = duration % 60;
    const minutes = Math.trunc(duration / 60);
    embed.addField(`[${title}](${url})`, `Duration ${minutes}:${seconds}`);
    console.log(embed.fields);
    }catch(err){
       console.log("Error occur in embed");
     }
    }

  console.log(embed.fields);
  return embed;
}
Sign up to request clarification or add additional context in comments.

1 Comment

But the fetchVideoInfo() is passing the result into the callback function, and i can't change that. It returns a promise that executes callback function, see the link i provided.
0

So there was an answer here, but it got deleted within 3 minutes but I still managed to try it and it worked! So I'm posting it here in hope that it can be useful!

async function generatePlayList() {
  const queue = this.queue;
  const date = new Date();
  let embed = new Discord.RichEmbed();
  embed
    .setTitle("Playlist")
    .setColor("#25473A")
    .setDescription("Music currently in playlist!")
    .setAuthor(this.bot.user.name, this.bot.user.avatarURL)
    .setFooter("Time ")
    .setTimestamp(date);

  let promises = queue.map(id => {
    return new Promise((resolve, reject) => {
      fetchVideoInfo(id, (err, info) => {
        if (err) return reject(err);
        const { duration, title, url } = info;
        const seconds = duration % 60;
        const minutes = Math.trunc(duration / 60);
        const fieldTitle = `${title.replace(/ *\([^)]*\) */g, "")}`;
        embed.addField(fieldTitle, `Duration ${minutes}:${seconds}`);
        resolve(embed);
      });
    });
  });

  return Promise.all(promises).then(values => {
    return values[values.length - 1];
  });
}

What I did here is make an array promises, containing a Promise for each field I have to add to the object. Each promise is resolved inside of callback function for fetchVideoInfo, so it has to finish the callback in order to resolve. Then I return Promise.all, which resolves each Promise array promises and returns array of values. Since the last resolved Promise is the one containing the latest object I pick it with values[values.length - 1] and return. Promise.all, will return value which is returned from then callback function.

And when I call the function i call it like this:

generatePlayList(array).then((embed)=>{
    //Do whatever
})

Or like this:

async function stuf(){
   let embed = await generatePlayList(array);
   //Do whatever
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.