Right way of delaying execution synchronously in JavaScript without using Loops or Timeouts!

There is a huge debate of using delays in JavaScript. One such thing is totally covered by SitePoint in their article, Delay, sleep, pause, wait etc in JavaScript. Before ECMA Script 5, we had only two ways of introducing delays in JavaScript.

  • Using an infinite loop that runs till the right time is satisfied.
  • Using a setTimeout timer.

Unfortunately, both the above methods are pretty messed up. When you are using an infinite loop, you literally freeze your browser to death by screwing up the thread that runs your JavaScript code. Even a high-end macOS system will generate a hole below with the heat produced by running such scripts. Also, most of the browsers are smart enough to find out an infinite loop and explicitly crash a tab.

On the other hand, the setTimeout seemed to be a pretty safe alternative to the infinite loop. Unfortunately, it's not synchronous. You won't be able to execute code that has to be executed in the procedural method. Having said this, it's definitely going to break the execution order or logic and with the amount of callbacks it might produce, this won't be the right method to go ahead.

Problem

Until today, I was happily using setTimeout and a number of callback functions in my code. I had to write a Jest test case, where I had to wait till an AJAX response is completed. For some reason, I am unable to make it work using a callback function as I won't be able to use an asynchronous function. This is the same reason, I am unable to test for AJAX calls.

I had to find a way of completing the test case, without the use of infinite loops or setTimeout. We have already discussed the issues that infinite loops cause and the problem the latter is it is an asynchronous function. I won't be able to use callbacks in Jest because of the fact that it is completely synchronous and when I use an asynchronous function, it just executes it, doesn't wait till it is over and drops the thread.

Solution

I was really stuck until I hacked up using await keyword. An interesting thing is that this keyword makes asynchronous Promise() objects to behave synchronously. To be precise, it waits till the asynchronous call is completed (making it synchronous) and then moves on to execute the next step. This fired up an idea in me. Why not I create a fake Promise() (we all do it) and then make the script delay the execution for a few moments?

Here's one solution I tried and it worked charmingly awesome.

console.log("Start");  
console.time("Promise");  
await new Promise(done => setTimeout(() => done(), 5000));  
console.log("End");  
console.timeEnd("Promise");  

When the above code was executed in the Chrome JavaScript Console, the results were exciting.

Chrome Dev Tools

It takes a few more milliseconds, which is extremely good for me. The only thing I need to make sure is that the JavaScript interpreter that I am using should be supporting async & await keywords and Promise(). To make things simple, I can just make a better, reusable version of the above code for everyone to use.

function delay(n) {  
  n = n || 2000;
  return new Promise(done => {
    setTimeout(() => {
      done();
    }, n);
  });
}

Getting this to run in console, I get.

Chrome Dev Tools

If at all I am using this delay code in any of my functions, all I need to do is, the function that's going to call this delay() should be defined as asynchronous function. You can have a quick example in the below code on how I managed to get the test passed.

it("Should perform a synchronous asynchronous request", async () => {  
  console.log("Start");
  console.time("Promise");
  // Fire an AJAX call that resolves from 500ms to 3.50 seconds.
  let AJAXCall = fetch("/some/api/endpoint");
  let AJAXResp = "";
  AJAXCall.then((resp) => {
    // Set the response
    AJAXResp = resp.Name;
  });
  await delay(5000);
  console.log("End");
  console.timeEnd("Promise");
  expect(AJAXResp).toBe("Works!");
});

Let me add a quick demo here:

And the test waits for five seconds before it hits the expect() and it is synchronous and my test passed! This is one of the best ways to delay the execution of JavaScript without having to use infinite loops or asynchronous functions. Hope this is helpful.



comments powered by Disqus