Priyath Gregory
Priyath Gregory

Reputation: 987

Which part of the Javascript runtime is responsible for putting the callback into the callback queue?

Consider this code running on the chrome console:

function main(){
  setTimeout(()=>console.log('Hello World!'), 5000);
};

main();

As per my understanding:

  1. The V8 engine will push main() into the call stack.
  2. The setTimeout() web API binding will be called by the engine, which in turn will trigger some native code outside of the main javascript thread.
  3. main() will be popped off the stack
  4. Once the 5 seconds has elapsed, the event loop will retrieve the callback from the callback queue and add it onto the call stack for execution.

My question (which I think is a very minute detail, but has been bugging me for awhile) at which point and by whom is the callback pushed onto the callback queue?

Upvotes: 2

Views: 474

Answers (2)

jmrk
jmrk

Reputation: 40581

You can think of the event loop as this piece of pseudo-code at the core of the JavaScript engine:

while (true) {
  while (queue.length === 0) {
    sleep();  // Native way to let the current process sleep.
  }
  callback = queue.pop_first();
  callback();
}

And then the engine exposes a public function to its embedder:

function ScheduleCallback(callback) {
  queue.push_last(callback);
}

I'm obviously glossing over a bunch of details here (synchronization of queue access, waking up when something gets queued, graceful termination, prioritization of callbacks, ...), but that's the general gist of it.

To implement setTimeout, an embedder would use some other primitive that provides a waiting function (e.g. it could spin up a thread and put that thread to sleep for the desired time, or it could rely on a function similar to setTimeout provided by the operating system, i.e. some way to trigger a callback after a specified amount of time), and then call the function mentioned above. In pseudo-code:

engine.global.setTimeout = function(callback, delay) {
  OS.kernel.setTimeout(delay, () => {
    engine.ScheduleCallback(callback);
  });
}

This also explains why timeouts are not guaranteed to be precise: firstly, the operating system's timer primitive might have its own granularity constraints and/or might simply be busy; secondly the best an embedder can do is to schedule the callback after the specified time, but if your event loop is currently busy executing something else, then the scheduled callback will have to wait in the queue for its turn.

Side note: "pushing a function onto the call stack" is exactly the same as calling it; "popping it off the call stack" is exactly the same as having it return. The line "callback();" in the first snippet above does both. It's that simple!

Upvotes: 2

t.niese
t.niese

Reputation: 40872

As JavaScript is not multithreaded, a callback can't be called at any time or run in parallel to other code of the same "context".

As of that a callback function that is passed to some external code can't be called at any time by that external code, that call has to be initiated by the engine. To give the code execution some predict/reliable execution order, the engine uses the even loop for that.

How this is handled depends on the engine, the engine could tell the external code to check if there are callbacks to be called, and ask to call them. But that might result in undesirable groupings of callbacks. Or that external code could pass those callbacks that should be called back to the engine which stores them in a queue.

Upvotes: -1

Related Questions