Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Stacks of Fun with Timers and the Event Loop

Stacks of Fun with Timers and the Event Loop

Delving into browser internals: the stack, web APIs and how the event loop works. Discover why `setTimeout(fn, 0)` gets used. Originally a +rehabstudio FE night class from 2016.

Neil McCallion

February 29, 2016
Tweet

More Decks by Neil McCallion

Other Decks in Programming

Transcript

  1. What’s in the browser? Something that runs our code Things

    to code with Something to schedule and render it all properly ?
  2. What’s in the browser? Something that runs our code Things

    to code with Something to schedule and render it all properly ? JavaScript runtime Web APIs Callback queue Event loop
  3. DID YOU KNOW? ? The JavaScript runtime (V8, Rhino, etc)

    doesn’t contain setTimeout, XMLHttpRequest, document, or anything like that. The browser provides these APIs separately, outside of the runtime.
  4. What’s in the browser? JavaScript runtime Callback queue Web APIs

    Call stack Event loop e.g. V8, Rhino, etc. Parses and runs the JS by pushing functions onto the stack. Tracks which function we’re currently executing. All the cool stuff the browser gives us to build web apps. DOM, XHR, setTimeout, device APIs, etc... Stores callbacks from Web APIs until the event loop can handle them. Pushes callbacks from the queue onto the stack. Rendering
  5. The runtime stack Tracks which part of the program we’re

    currently executing. Functions get pushed onto the top of the stack and get popped off when executing. Think of a plate of pancakes. You put the pancakes onto the top of the pile and take one off when you want to eat them. Or an array where you can’t access by index or splice/slice, just push() and pop().
  6. The JavaScript runtime is single-threaded.* This means it has just

    one call stack. It can only do one thing at a time.* If something blocks the stack, the program (and browser in general) will slow down or halt. *sort of a lie. Also, don’t mention Web Workers.
  7. Code function add(a, b) { return a + b; }

    function liquid() { var x = add(2, 2); console.log('Snaaaake!', x); } function solid() { liquid(); console.log('Brother!'); } solid(); Pen Interactive demonstration of the stack during code execution Pen
  8. Stack trace in DevTools when exceptions or console.trace() called The

    (anonymous function) in this case is our main program.
  9. Blocking the stack = bad Since we have one stack,

    it stands to reason that you shouldn’t flood it, or block it with slow code. If you flood the stack (e.g. with a recursive function), the browser will lock up and eventually terminate your program with a ‘call stack size exceeded’ (stack overflow) error. If you run slow code on the stack, the browser can’t do anything else like handle input or re-render until the code has finished. function ohNo() { ohNo(); } ohNo(); // stack overflow error (main program) updateSomething() shittySlowCode() X
  10. Where do the timeouts go? We run code with a

    setTimeout() call included. The call gets pushed onto the stack as usual, but then pops off and mysteriously vanishes. The rest of our program executes fine. A short time later, our setTimeout() callback reappears and is executed. How is this possible if we only have one stack in our runtime? console.log('Hello there'); setTimeout(function() { console.log('Hello once more'); }, 5000); console.log('Hello again'); < about 5 seconds later... ?
  11. JavaScript in the browser is more than just the runtime.

    We have Web APIs and a queue to handle things off-stack (i.e. off our single thread).
  12. Web APIs The browser provides us with a load of

    APIs outside the JavaScript runtime for programming web apps. A lot of these APIs are asynchronous e.g. they do their heavy lifting outside of the main runtime stack so as not to slow everything down. When they’re complete, they pass their callbacks onto the callback queue for processing the next time the stack empties. This is the ‘trick’ behind setTimeout(fn, 0).
  13. The event loop The event loop runs every frame. Its

    job is to push the next item in the callback queue onto the runtime stack so it can be executed. If the queue is empty, it just idles and triggers rendering per frame. It will only push a callback the next time the stack is empty. If there are items holding up the stack (e.g. slow functions), the event loop won’t be able to empty the callback queue. This is why setTimeout is not a guaranteed delay - it’s the guaranteed minimum time before the callback will execute.
  14. setTimeout(fn, 0) Forces fn() into the callback queue via the

    Web API handler immediately (0 delay). It will sit there until the stack is next empty and the event loop is able to push fn() out of the queue and onto the stack. In other words, it forces the browser to wait until any blocking code has run before running fn(). This is a perfectly valid, if hacky-feeling, way to use setTimeout. - Waiting for DOM manipulations & repaints - Allowing intensive code to complete first - ‘Yielding’ to the browser during a heavy loop
  15. setTimeout(fn, 0) function clear() { myEl.classList.remove('fadeable', 'visible'); } function addClasses()

    { clear(); myEl.classList.add('fadeable'); myEl.classList.add('visible'); } function addClassesAsync() { clear(); setTimeout(function() { myEl.classList.add('fadeable'); myEl.classList.add('visible'); }, 0); } • Simple example • More complex example with transitionEnd listeners