skip to Main Content

I’m trying to access an API to pull stock data. The free plan only lets me make 5 calls per minute. I’m trying to use native JS/jQuery to make the calls. Using some example code I’ll illustrate my issue (using 3 second intervals). I initially thought that putting setTimeout() within a for-loop that prints to the console would print, wait 3 seconds, print, wait 3 seconds, repeat… But what it does is wait 3 seconds, and then prints all the lines at once. I understand this now. It seems bad practice to "lock up" execution of a program, but I’m starting to think that is what needs to be done here?

$(function() {
    for (var i = 0; i < 5; i++) {
        doSetTimeout(i);
    }
});

function doSetTimeout(i) {
  setTimeout(function() { console.log(i); }, 3000);
}

I also tried "locking up" the program with a while-loop and time delay. But my entire browser just froze and became unresponsive for much longer than the time inputted.

function pauseExecution(miliseconds) {
  var currentTime = new Date().getTime();
     while (currentTime + miliseconds >= new Date().getTime()) {
     }
}

2

Answers


  1. As others have mentioned, setTimeout is async. What is happening is you are scheduling 5 tasks to complete after roughly 3000ms. What you should do is not queue another task until the previous one has completed. This can be achieved in about a million way.

    Async/Await

    If you can, just use async/await. Its the easiest with the least amount of code.

    const delay = (ms) => {
      return new Promise((resolve) => {
        setTimeout(() => resolve(), ms);
      }, ms);
    };
    
    $(async function() {
      for (var i = 0; i < 5; i++) {
        await delay(3000);
        console.log(i);
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    Promise

    Creating a promise chain where every task is dependent on a delay and a unit of work would serve a similar purpose as async/await if you are not transpiling ect. You have to be careful with catchect to make this production ready.

    const delay = (ms) => {
      return new Promise((resolve) => {
        setTimeout(() => resolve(), ms);
      }, ms);
    };
    
    $(function() {
      let task = delay(3000);
      const work = (i) => console.log(i);
    
      for (var i = 0; i < 5; i++) {
        let j = i; // you need this because of closures.
        
        task = task
          .finally(() => {
            work(j); 
          })
          .then(() => delay(3000));
      }
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    Queues

    This is probably the most complex solution. You create a blocking queue. All tasks can be queued whenever but the queue will only execute the next task after the current task calls next. This is not production ready and is meant to serve as an example. There are issues with stack overflows with sync operations as well as exception handling.

    const queue = () => {
      const tasks = [];
      let running = false;
      
      const add = (task) => {
        tasks.push(task);
        run();
      };
      
      const run = () => {
        if (!running) {
          running = true;
          
          const step = () => {
            if (tasks.length > 0) {
              tasks.shift()(step);
            } else {
              console.log('done');
              running = false;
            }
          };
          
          step();
        };
      };
      
      return { add, run };
    };
    
    const q = queue();
    
    for (let i = 0; i < 5; i++) {
      q.add((next) => {
        const j = i; // again because of closure scoping.
        
        setTimeout(() => {
          console.log(j);
          next();
        }, 3000);
      });
    }

    SetInterval

    If you dont need to block on previous operations(ajax/fetch) you could simply use a setInterval. This just schedules a task evey Nms regardless of the state of the previous tasks.

    let i = 0;
    const interval = setInterval(() => {
      if (i === 5) {
        clearInterval(interval);
      } else {
        console.log(i++);
      }
    }, 3000);
    Login or Signup to reply.
  2. Ohh I have already done something like that, What you need to do is run and wait for the func to end.

    See this example to understand.

    function doSomthing(){
    console.log("Somthing is done");
    Start();
    }
    
    function Start(){
     setTimeout(doSomthing, 3000);
    }
    
    Start();
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search