user3439521
user3439521

Reputation:

NodeJS - how to make forEach and for loop functions sequential

server.queue.forEach(function(q) {
    YTDL.getInfo(q, (error, info) => {
        console.log(info["title"]);
        message.reply('"' + info["title"] + '"');
    });
});

for (var i = 0; i < server.queue.length; i++) {
    YTDL.getInfo(server.queue[i], (error, info) => {
         console.log(info["title"]);
         message.reply('"' + info["title"] + '"');
    });
}

I'm creating a music bot for a VoIP called Discord using Node.js and whenever either of the loops above execute, they print in a random order. How do I make it so that they are printed sequentially (server.queue[0], server.queue[1], server.queue[2]...)? YTDL is a package called ytdl-core that downloads YouTube videos as well as display the info such as the title of a video using the video link. server.queue is an array of YouTube video links.

Upvotes: 3

Views: 9493

Answers (3)

num8er
num8er

Reputation: 19372

Just to add why you try to make it sequential: YouTube has rate limits and etc limitations which can be easily broken if there will be parallel calls.

So for that reason we are not using Promise.all or .allSettled.

Solution to question based on modules and language features of stack for the year 2017:

  1. install: npm i --save async

  2. and the code:

const async = require('async');
    
async.eachSeries(
  server.queue,
  (q, next) => {
    YTDL.getInfo(q, (error, info) => {
        console.log(info["title"]);
        message.reply('"' + info["title"] + '"');
        next();
    });
  }
});

for..loop is not good solution for asynchronous stuff - it will call them and run next statements that comes after for loop.


Question is dated for the year 2017, for that moment there was no proper support of async/await methods and etc. Plus people were staying at LTS versions of Node.js which slowed down progress.

Upvotes: 2

NaWeeD
NaWeeD

Reputation: 609

I was struggling with this problem for a while to find the best solution until I found out the built-in yield feature represented in ECMA6. so using gen-run library you can do the following:

let run = require('gen-run');

function notSequential(){
    for (let i = 0; i < 3; i++)
        f(i * 1000);
    console.log("it won't print after for loop");
}

function sequential(){
    run(function*(){
        for (let i = 0; i < 3; i++)
            yield changedF(i * 1000);
        console.log("it will print after for loop");
    });
}

function f(time) {
    setTimeout(function(){
        console.log(time);
    }, time);
}

function changedF(time) {
    return function (callback) {
        setTimeout(function(){
            console.log(time);
            callback();
        }, time);
    }
}

notSequential();

it won't print after for loop 0 1000 2000

sequential();

0 1000 2000 it will print after for loop

Upvotes: 0

AbhinavD
AbhinavD

Reputation: 7292

If you don't want to use the async library, you can use Promise.all like this

const youtubeData = [];
for (var i = 0; i < server.queue.length; i++) {
  youtubeData.push(YTDL.getInfo(server.queue[i]));
}

Promise.all(youtubeData).then((values) => {
  // These values will be in the order in which they are called.
});

Note that Promise.all will wait for all the queries to finish or reject everything whenever one request fails. Look at your use case and select accordingly.

As per library, it returns promise if no callback is provided

ytdl.getInfo(url, [options], [callback(err, info)])

Use this if you only want to get metainfo from a video. If callback isn't given, returns a promise.

Upvotes: 0

Related Questions