Maor Bachar
Maor Bachar

Reputation: 81

loop and setTimeout function still not working after some research

I did some research about loops and setTimeout function, but still its not working as I wish...

It opens all the links in same time without delay of 5 seconds per link that opened.

I would like it to open every link with 5 second delay after every each of them.

Code:

var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]')
for (var i = 1; i <= links.length; i++) {
    (function(index) {
        setTimeout(function() { 
            window.open(links[index].href,'_blank'); 
        }, 5000);
    })(i);
}

Upvotes: 1

Views: 157

Answers (5)

CodeWizard
CodeWizard

Reputation: 142472

From the code above i assume that you wish to open the links once every 5 seconds so here are the improvements and suggestion made upon your code

// use JS hoisting for variable declarations
var 
    // Timer to store the next timer function reference
    timer,

    // The delay between each function call
    delay = 5000,

    // Set the desired selector 
    selectors = 'a[class="mn-person-info__link ember-view"][id^="ember"]',

    // Get the list of the required selectors.
    links = document.querySelectorAll(selectors);

// Create a function which will be called every X seconds
function openLink( index ){
    
    // validate that the index is not out of bound
    if (index === links.length){ 
        return;    
        }
    
    // get the current link and open new window with the link url
    window.open(links[index].href,'_blank'); 
 
    // Set the next timer to open the next window
    timer = setTimeout( openLink, delay, ++index);  
}

// Call the function for the first time with index = 0
openLink( 0 );

What does this code do?

The first section is declaration of the variables which will be used in this script. The preferred way to declare variables id to to use hoisting

Hoisting

Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.

Timers

If you wish to open the links in a sequence you should put them inside a function which will call them one after the other instead of using the for loop. The for loop will place all of them in the call stack/event loop and all of them will be executed after 5000 milliseconds since this is what set time out will so, it will schedule the execution of the code to 5000 milliseconds from now for all of them.

Im recommending you to watch the amazing lecture by Philip Roberts
Philip Roberts: What the heck is the event loop anyway

Saving the return value from the setTimeout will allow you to later on cancel the timer if you wish using the clearTimeout( timer )

Summary

Since you had a for loop it will simply loop over all your links. The setTimeout will set the scheduled execution time 5 seconds from now for all the links.
The trick here is to set the next timer after the current one is opened. This is why the sertTimeout is defined within the function itself.

The setTimeout gets a third param which is the parameter passed to the function when its being called.

Upvotes: 2

cnexans
cnexans

Reputation: 994

Easy way:

links.forEach(function(i, link) {
    setTimeout(function() { 
        window.open(link.href,'_blank'); 
    }, 5000 * i);
});

Just wait i * 5 seconds, where i is the index of the link

Upvotes: -2

Patrick Roberts
Patrick Roberts

Reputation: 51946

Using a Promise chain and Array#reduce(), you can do this:

var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');

Array.from(links).reduce((chain, { href }) => {
  return chain.then(() => new Promise(resolve => {
    window.open(href, '_blank');
    setTimeout(resolve, 5000);
  }));
}, Promise.resolve())

If you don't want to do something quite this fancy and you're fine setting all your timeouts at once, you can simplify your for loop using let instead of var and an IIFE:

var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');

for (let i = 0; i < links.length; i++) {
  setTimeout(function() { 
    window.open(links[i].href, '_blank'); 
  }, 5000 * i);
}

Or even simpler, using for...of and object destructuring:

var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]');
var i = 0;

for (const { href } of links) {
  setTimeout(function() { 
    window.open(href, '_blank'); 
  }, 5000 * i++);
}

Upvotes: 4

guijob
guijob

Reputation: 4488

setTimeout is asyncronous. I would solve this specified problem with:

Array.from(links).reduce((a,e)=>{
    setTimeout(function() { window .open(e.href,'_blank');}, a);
    return a + 5000;
} ,5000);

Upvotes: 2

Alex I.
Alex I.

Reputation: 111

That's because all your timeouts are set immediately, almost at one time. So the ends of timeouts are take place almost on the same time. Try this:

var links = document.querySelectorAll('a[class="mn-person-info__link ember-view"][id^="ember"]')

for (var i = 1; i <= links.length; i++) {
  (function(index) {
    setTimeout(function() {
      window .open(links[index].href,'_blank');
    }, 5000 * i);
  })(i);
}

Upvotes: 2

Related Questions