Why would the following JS code not terminate in a browser?

Viewed 137
function foobar() {
  console.log('baz');    
  setTimeout(() => foobar(), 1000);
}

foobar();
throw new Error('terminate');

I would assume that the foobar function queues a callback that is executed after 1s, exits the stack and then the main function throws an error and things should terminate. However they don't if run in a browser.

2 Answers

It's even better illustrated by this example:

let attempts = 10;
function foobar() {
  if (!attempts--) return; // make it stop!
  console.log('baz');
  setTimeout(foobar, 1000);  
  throw new Error('terminate');
}
foobar();

See, throwing an Error terminates execution of the current task, but doesn't terminate the browser event loop. And event loop already has another task sitting in its queue - scheduled by setTimeout before Error has been thrown. Rinse and repeat.


Things are different in Node.js land, however: the very first uncaught exception basically stops execution of the whole process. That's the philosophy of Node - fail early - and, though questioned, it exists the way it is.

Still, with minor modifications you'll see the similar picture. Just add these lines to the script:

process.on('uncaughtException', function (err) {
  console.error('CAUGHT', err);
});

... and you'll see quite similar pattern.

Be the JavaScript interpreter.

Let's see the list of tasks, the JavaScript interpreter have to do :


TaskList

[Execute main code]


Now the interpretor execute the task, line by line.

Executing foobar(); the JavaScript interpreter is going to push a new task in it's task list, which is containing the setTimeout() function to execute.


TaskList

[Execute main code (in progress)][Execute setTimeout function]


Then when it reach the throw, it throw an error and terminate the actual task execution.

It takes the next task and execute it :


TaskList

[Execute setTimeout function (in progress)]


Executing the setTimeout function, it pushes again a new task (the same it just executed).

And again, and again, and again, and again...


    function foobar() {
      console.log('baz');    
      setTimeout(() => foobar(), 1000);
    }
    
    foobar();
    throw new Error('terminate');

Related