Roberts Shows Browser Web APIs Drive Concurrency Outside Chrome V8

What the heck is the event loop anyway? | Philip Roberts | JSConf EU — JSConf

JSConf

Because JavaScript engines execute strictly on a single call stack, developers must offload slow tasks through browser Web APIs to prevent synchronous code from blocking the render loop.

AI-assisted summary of What the heck is the event loop anyway? | Philip Roberts | JSConf EU — original source. Check the original for full context.

Source published:

Key ideas

  • The core JavaScript runtime (such as V8) contains only a call stack and a memory heap; essential asynchronous APIs like setTimeout, DOM manipulation, and XMLHttpRequest are provided externally by the browser environment as Web APIs. (2:40)
  • The event loop acts as a coordinator between the task queue and the call stack, pushing a waiting callback onto the stack only after the stack has completely emptied. (12:19)
  • Timers like setTimeout specify a minimum time to execution rather than a guaranteed time, meaning a zero-millisecond timer simply defers execution until ongoing synchronous execution finishes. (14:55)
  • Browser rendering targets 16.6ms intervals (60 fps) and takes priority over task queue callbacks, but synchronous blocking operations prevent repaints entirely, freezing the UI. (22:15)

Detailed notes

## Background Published by the JSConf channel on YouTube on October 9, 2014, this presentation features Philip Roberts demystifying the core mechanics of JavaScript. According to the video description, the talk tackles ubiquitous buzzwords like "event-loop," "non-blocking," and "asynchronous callbacks" using visual tools and hacks to build an intuitive mental model of how JavaScript actually runs.

## TL;DR Roberts clarifies JavaScript's execution model by separating the V8 engine—which contains only a single-threaded call stack and a memory heap—from the surrounding browser architecture of Web APIs, the task queue, and the event loop. He demonstrates how long-running synchronous code monopolizes the stack and freezes UI repainting, contrasting this with asynchronous APIs that offload work so the main thread stays unblocked. The event loop acts as the coordinator, waiting until the call stack is completely clear before pushing completed callbacks from the task queue back onto the stack. Using his custom interactive visualization tool, Loupe, Roberts illustrates concepts like `setTimeout(..., 0)`, the 16.6ms browser rendering cycle, and the necessity of debouncing high-frequency events to maintain fluid performance.

## Atmosphere & style The presentation is refreshingly humble, humorous, and engaging, beginning with a spontaneous audience stretch and Roberts candidly admitting that he did not truly understand JavaScript's inner workings despite years as a professional engineer. His delivery is animated and self-deprecating, laughing about the ordeal of building a bespoke simulator after breaking hundreds of Keynote transitions. Watching the session feels like sitting down with a clever, pragmatic peer who strips away computer science pretense, transforming notoriously murky browser mechanics into vivid, accessible visual demonstrations.

## Bulletin - [02:40](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=160s) The core JavaScript runtime (such as V8) contains only a call stack and a memory heap; essential asynchronous APIs like setTimeout, DOM manipulation, and XMLHttpRequest are provided externally by the browser environment as Web APIs. - [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) The event loop acts as a coordinator between the task queue and the call stack, pushing a waiting callback onto the stack only after the stack has completely emptied. - [14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) Timers like setTimeout specify a minimum time to execution rather than a guaranteed time, meaning a zero-millisecond timer simply defers execution until ongoing synchronous execution finishes. - [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) Browser rendering targets 16.6ms intervals (60 fps) and takes priority over task queue callbacks, but synchronous blocking operations prevent repaints entirely, freezing the UI.

## Key takeaways - [02:40](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=160s) JavaScript execution engines like Google Chrome's V8 contain only a memory heap and a single call stack, omitting native event loops or timer APIs. - [04:16](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=256s) JavaScript is single-threaded and executes one stack frame at a time using a Last-In, First-Out (LIFO) call stack structure. - [06:35](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=395s) Exceeding call stack limits through infinite recursion triggers browser errors to halt runaway execution before system resources collapse. - [07:18](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=438s) Synchronous blocking operations monopolize the call stack and completely freeze the browser, halting user interactions, UI repainting, and element selection. - [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) Browser concurrency is achieved by delegating operations like network requests, DOM events, and timers to background Web APIs outside the JavaScript thread. - [13:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=795s) Completed asynchronous Web API tasks push their callbacks into the task queue rather than modifying the active call stack directly. - [13:40](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=820s) The primary role of the event loop is to check whether the call stack is empty and, if so, transfer the first waiting callback from the task queue onto the stack. - [14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) Using `setTimeout(fn, 0)` is a deliberate pattern to defer execution until all synchronous operations currently on the call stack have cleared. - [18:50](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1130s) Timer delays represent a minimum time before execution rather than an exact schedule, as queued callbacks must wait for preceding callbacks and stack frames to finish. - [21:10](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1270s) Callbacks can be synchronous (like Array.prototype.forEach running immediately on the stack) or asynchronous (deferred through the task queue). - [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) Browsers attempt to repaint the screen every 16.6 milliseconds (60 frames per second), but a blocked call stack delays the render loop. - [23:45](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1425s) Chunking long-running computational work into asynchronous timeouts allows the call stack to clear between steps, enabling smooth 60fps UI repaints. - [24:35](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1475s) High-frequency DOM events such as scrolling can saturate the task queue with rapid callbacks, causing UI stutter unless managed through debouncing. - [25:45](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1545s) Roberts constructed the Loupe visualizer by parsing JavaScript with Esprima, injecting artificial blocking loops into the AST, and isolating execution in a Web Worker.

## Detailed walkthrough ### [00:01](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1s) Introduction and the 18-Month Journey

The session is introduced as the final talk before the afternoon break at JSConf EU. Philip Roberts, introduced as working at &yet and hailing from Edinburgh, Scotland, takes the stage in a packed room. To shake off the awkwardness, he asks the audience to join him in a quick stretch: "I needed to stretch, so I look less weird." He frames the central question of his presentation: what actually is the event loop inside JavaScript?

Roberts explains that he works for &yet, an American development shop specializing in real-time web technologies. He shares that roughly 18 months prior, despite being a paid professional JavaScript developer, he had an unsettling realization: “I thought to myself how does, like JavaScript actually work? And I wasn't entirely sure.” He had heard terms like Google Chrome's V8 runtime, single-threaded execution, and callbacks, but he did not deeply understand how those concepts functioned together. When he began researching, standard explanations described JavaScript as a single-threaded concurrent language equipped with a call stack, an event loop, a callback queue, and other APIs. Because he did not have a formal computer science degree, these buzzwords felt opaque. When he investigated V8's source code directly to see these components in action, he discovered that V8 contained only a call stack and a heap—the event loop, callback queue, and Web APIs were entirely absent. It took him 18 months of reading, experimenting, and building mental models to demystify how these systems truly interact.

- Philip Roberts works at &yet, a development shop focused on real-time web products. - The talk aims to help both newcomers understand why JavaScript relies so heavily on callbacks and give experienced engineers a clearer picture of their runtime environment.

[02:40](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=160s) The Runtime vs. The Bigger Picture

Roberts reveals the internal anatomy of a typical JavaScript engine like Chrome's V8. At its core, the runtime provides two primary constructs: the memory heap (where memory allocation occurs) and the call stack (where stack frames reside to track execution). However, cloning the V8 codebase and searching for everyday primitives like `setTimeout`, DOM APIs, or HTTP requests yields zero results. They simply do not exist inside V8 itself.

```mermaid flowchart TD accTitle: JavaScript Browser Runtime Architecture accDescr: Diagram illustrating the separation between the V8 runtime, browser Web APIs, callback queue, and event loop.

subgraph Browser ["Web Browser Environment"] subgraph V8 ["V8 Runtime"] Heap["Memory Heap\n(Allocation)"] Stack["Call Stack\n(Stack Frames)"] end

subgraph WebAPIs ["Web APIs (Provided by Browser)"] DOM["DOM (document)"] Ajax["AJAX (XMLHttpRequest)"] Timer["Timers (setTimeout)"] end

Queue["Callback Queue\n(onClick, onLoad, onDone)"] Loop["Event Loop"] end

Stack -->|"calls Web API"| WebAPIs WebAPIs -->|"pushes callback"| Queue Queue -->|"monitored by"| Loop Loop -->|"pushes callback when empty"| Stack ```

This realization leads Roberts to the broader architectural reality of the browser environment. The V8 engine does not operate in isolation; it sits alongside browser-provided Web APIs (such as the DOM, `XMLHttpRequest`, and timer methods), the callback queue, and the event loop. While developers often hear these phrases tossed around interchangeably, understanding how each part coordinates is essential for understanding JavaScript execution.

[04:16](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=256s) How the Call Stack Works

Roberts begins from the fundamentals: JavaScript is a single-threaded runtime with a single call stack, meaning it can execute only one piece of code at a time. The call stack operates as a Last-In, First-Out (LIFO) data structure that keeps track of the program's execution point: stepping into a function pushes a stack frame onto the stack, while returning from a function pops the top frame off.

```mermaid flowchart TD accTitle: Call Stack Execution Order accDescr: Step-by-step visual of stack frames pushing and popping during execution of printSquare.

subgraph Step1 ["1. Initial Call"] S1["printSquare(4)"] M1["main()"] S1 --- M1 end

subgraph Step2 ["2. Push square"] Q2["square(4)"] S2["printSquare(4)"] M2["main()"] Q2 --- S2 --- M2 end

subgraph Step3 ["3. Push multiply"] U3["multiply(4, 4)"] Q3["square(4)"] S3["printSquare(4)"] M3["main()"] U3 --- Q3 --- S3 --- M3 end

subgraph Step4 ["4. Log Result"] L4["console.log(16)"] S4["printSquare(4)"] M4["main()"] L4 --- S4 --- M4 end

Step1 --> Step2 --> Step3 --> Step4 ```

To demonstrate, he steps through a simple script containing three functions: - `multiply(a, b)`: Returns the product of two numbers. - `square(n)`: Calls `multiply(n, n)`. - `printSquare(n)`: Invokes `square(n)` and then logs the output via `console.log`.

When this file executes: 1. The global wrapper (`main` / anonymous function) is pushed onto the stack. 2. The runtime reaches the top-level call `printSquare(4)`, pushing `printSquare` onto the stack. 3. Inside `printSquare`, it calls `square(4)`, pushing `square` onto the stack. 4. `square` calls `multiply(4, 4)`, pushing `multiply` onto the stack. 5. `multiply` executes its return statement and pops off. 6. Execution resumes in `square`, which finishes and pops off. 7. `printSquare` continues and calls `console.log`, which pushes onto the stack, logs the value, and pops off implicitly upon reaching the end of the function block. 8. Finally, `printSquare` and `main` pop off, leaving the stack clear.

Roberts points out that developers regularly encounter the call stack through stack traces. When a chain of function calls like `baz()` calling `bar()`, which calls `foo()`, encounters an uncaught error, Chrome prints the full hierarchy: `foo`, `bar`, `baz`, down to the anonymous `main` function. Similarly, infinite recursion demonstrates "blowing the stack": calling a recursive function `foo()` with no base case rapidly pushes frames until the browser intervenes. As Roberts notes, “ultimately Chrome says, you probably didn't mean to call foo 16,000 times recursively, I'll just kill things for you and you can figure out where your bug lies, right.”

[07:18](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=438s) The Danger of Blocking the Stack

Once call stack mechanics are established, Roberts poses the critical issue: what happens when code running on the stack is slow? The developer community frequently discusses "blocking," but there is no strict technical boundary for what qualifies as blocking—it simply refers to any slow operation occupying the stack. Operations like `console.log` are fast, whereas looping from one to ten billion, processing heavy images, or waiting on network operations are slow.

To illustrate, Roberts presents a hypothetical scenario using a synchronous network request like `getSynchronous('foo.com')`. If network requests operated synchronously on a single-threaded stack: 1. The program calls `getSync` and is forced to wait while the physical network round-trip completes. 2. The computer remains frozen on that stack frame until the response arrives. 3. If an application makes multiple sequential synchronous requests, each one forces the thread to sit idle. 4. If a network request hangs or fails entirely, the thread is blocked indefinitely, with Roberts joking: “Wait, and, I mean, this network request might never finish, so... yeah, I guess I'll go home.”

In multi-threaded environments like Ruby, a thread can block while other threads handle work. But in a single-threaded runtime running inside a browser, blocking has severe consequences for the user interface. Roberts demonstrates this with a browser-based demo where synchronous behavior is simulated using a five-second `while` loop. While the loop executes, the browser window locks up entirely: the UI cannot re-render, clicks fail to register, and even the visual pressed state of the "Run" button fails to render. When the stack finally clears, the browser frantically catches up with all the backlog user interactions at once. “That's because if that call stack has things on it... the browser can't do anything else. It can't render, it can't run any other code, it's stuck.” To deliver responsive 60fps web applications, developers cannot leave heavy tasks sitting on the stack.

[10:22](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=622s) Asynchronous Callbacks and Browser Concurrency

The web platform's answer to this bottleneck is asynchronous callbacks. Instead of blocking the single thread on long-running tasks, almost all browser and Node.js APIs are designed asynchronously: code initiates a task, supplies a callback function, and continues executing without waiting for the task to finish.

Roberts illustrates this with a classic snippet: - `console.log('hi')` runs immediately. - `setTimeout(function() { console.log('there'); }, 5000)` is invoked. - `console.log('JSConfEU')` runs immediately afterward.

When this script runs, the output prints "hi", followed by "JSConfEU", and then five seconds later prints "there". Viewed purely through the lens of a single call stack, this behavior appears magical: `setTimeout` executes, does not log immediately, disappears from the stack, and then five seconds later its callback suddenly materializes on top of the stack.

This dynamic reveals why the common explanation of JavaScript is incomplete. As Roberts puts it: “Right, so I've been kind of partially lying to you and telling you that JavaScript can only do one thing at one time. That's true the JavaScript runtime can only do one thing at one time.” The runtime cannot run concurrent timers or network requests while processing other code. The actual mechanism that makes concurrency possible is that the browser environment comprises far more than just the JavaScript runtime itself.

[12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) The Browser Environment, Web APIs, and the Event Loop

Philip Roberts expands the mental model of the runtime beyond just the V8 engine and its single call stack. While JavaScript execution is single-threaded, the browser provides Web APIs that effectively act as separate threads you can make calls to. This is where concurrency actually enters the picture. For developers coming from a backend background, he points out that the architectural diagram looks virtually identical in Node.js, where C++ APIs replace Web APIs and thread management is hidden under the hood by C++.

```mermaid flowchart TD accTitle: JavaScript Runtime and Browser Architecture accDescr: Diagram showing how the call stack interacts with Web APIs, the task queue, and the event loop.

subgraph V8 ["V8 Runtime"] Stack["Call Stack"] end

subgraph Browser ["Browser Environment"] WebAPIs["Web APIs\n(DOM, setTimeout, XHR)"] TaskQueue["Task / Callback Queue"] end

EventLoop(("Event Loop"))

Stack -->|"Offload async task"| WebAPIs WebAPIs -->|"Push callback on complete"| TaskQueue TaskQueue -->|"Wait until Stack is empty"| EventLoop EventLoop -->|"Push callback"| Stack ```

Walking through the execution trace of an asynchronous snippet, running `console.log("hi")` pushes it directly onto the stack and outputs to the console. When `setTimeout` is invoked with a callback function and a delay, the runtime interacts with the browser's Web APIs. Because `setTimeout` is provided by the browser environment rather than living in the V8 engine source code, the browser registers the timer and manages the countdown independently. This allows the initial `setTimeout` call to finish immediately and pop off the stack, followed by `console.log("JSConfEU")`. Once the five-second countdown finishes, the Web API cannot simply force the callback directly onto the execution stack: “Now the web API can't just start modifying your code, it can't chuck stuff onto the stack when it's ready if it did it would appear randomly in the middle of your code so this is where the task queue or callback queue kicks in.”

Any completed Web API task pushes its associated callback function onto the task queue. At this stage, the event loop performs its role: “The event loops job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it.” With the stack clear, the event loop notices the waiting callback in the queue, moves it back into JavaScript territory on the call stack, executes `console.log("there")`, and clears the stack.

[14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) Demystifying `setTimeout(..., 0)` and Asynchronous Requests

Roberts addresses a common source of confusion for developers encountering asynchronous JavaScript: the `setTimeout(fn, 0)` pattern. Developers often wonder why anyone would wrap execution in a timer set to zero milliseconds if the goal is to run it immediately. He explains that `setTimeout(..., 0)` is designed to defer execution until the call stack has completely cleared of ongoing synchronous operations.

```mermaid flowchart TD accTitle: setTimeout Zero Execution Lifecycle accDescr: Trace of setTimeout zero executing through Web APIs and the task queue while the stack clears.

subgraph Stack1 ["1. Stack Processing"] ST["setTimeout(cb, 0)"] LogHi["console.log('hi')"] LogEU["console.log('JSConfEU')"] end

subgraph API ["2. Web API"] Timer["Timer (0ms) completes immediately"] end

subgraph Queue ["3. Task Queue"] CB["Callback cb() waits in queue"] end

subgraph Stack2 ["4. Stack Cleared"] RunCB["cb() -> console.log('there')"] end

ST -->|"Hand off"| Timer Timer -->|"Enqueue"| CB LogHi --> LogEU LogEU -->|"Stack empty"| RunCB CB -.->|"Pushed by Event Loop"| RunCB ```

Even with a zero-millisecond delay, the timer hands the callback off to the browser Web API, which immediately finishes and pushes the callback into the task queue. Because the event loop must wait until the stack is empty, subsequent synchronous statements—such as logging "hi" and "JSConfEU"—execute and pop off first. Only after the main stack is empty can the event loop step in to push the queued callback onto the stack. Roberts emphasizes that this pattern applies identically to AJAX requests: the code handling an `XMLHttpRequest` resides in the browser's Web APIs rather than the JavaScript runtime itself, allowing the stack to continue running uninterrupted while network operations take place.

[16:58](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1018s) Live Visualization with Loupe and Callback Mechanics

To make these mechanics tangible, Roberts reveals a tool he built called **Loupe**. He explains with a laugh that he originally wrote the talk for Scotland JS using roughly 500 animated Keynote transitions; after breaking half the slides, he “could not be bothered to redo all the slides because it was a total pain in the ass in keynote to do it so I took much easier route of writing a tool that can visualize the JavaScript Runtime at Runtime, and it's called Loupe.” Using Loupe, he demonstrates DOM event listeners and multiple `setTimeout` timers side by side.

When an event listener is added, it sits waiting in the Web API container until an interaction occurs. Clicking a button triggers the Web API to place the click callback onto the task queue. Roberts illustrates this by rapidly clicking a button dozens of times, showing that user interactions do not interrupt running code; each click callback merely appends to the queue and waits its turn.

He then shows four sequential calls to `setTimeout` configured with a 1000-millisecond delay. As the timers complete, their callbacks stack up in the queue. Because the event loop can only process them one at a time as the stack clears, the fourth callback ends up waiting well past its specified 1-second threshold. As Roberts notes, this demonstrates that `setTimeout` provides “not a guaranteed time to execution, it's a minimum time to execution, just like setTimeout zero doesn't run the code immediately it runs the code next-ish, sometime, right?”

Roberts also clarifies a fundamental terminology distinction regarding the word "callback": - **Synchronous callbacks**: Functions supplied as parameters to other functions that run immediately within the current call stack context (such as the standard `Array.prototype.forEach`). - **Asynchronous callbacks**: Functions passed to APIs that yield control, complete out-of-band, and are subsequently placed onto the callback queue to run in a future event loop tick.

[22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) Rendering Constraints, UI Blocking, and Flooding the Queue

Moving beyond simple execution traces, Roberts turns on an experimental feature he built to simulate browser repainting and rendering. While JavaScript runs, the browser aims to repaint the screen roughly every 16.6 milliseconds to achieve a smooth 60 frames per second. However, rendering cannot take place while synchronous JavaScript code occupies the call stack: “It has to wait till the stack is clear. The difference is that the render is given a higher priority than your callback, every 16 milliseconds it's going to queue a rend, wait till the stack is clear before it can actually do that render.”

```mermaid flowchart TD accTitle: Browser Render Loop Interaction accDescr: How synchronous blocking versus asynchronous chunking affects the browser render interval.

subgraph Sync ["Synchronous Array Processing"] LongStack["Stack Occupied by Slow Loop"] BlockedRender["Render Tick (16.6ms) BLOCKED"] LongStack -.->|"Prevents"| BlockedRender end

subgraph Async ["Asynchronous Chunking"] Task1["Stack: Process Item 1"] Yield1["Stack Empties"] RenderPass["Render Executes Smoothly"] Task2["Stack: Process Item 2"] Task1 --> Yield1 Yield1 --> RenderPass RenderPass --> Task2 end ```

When code executes a slow, synchronous loop over an array, the call stack remains monopolized. During this period, the browser render is completely blocked—the user interface freezes, text cannot be selected, and UI buttons stop responding. By contrast, refactoring slow operations into asynchronous chunks using deferred timeouts yields the stack back to the browser between items, affording the render process an opportunity to intervene and update the screen. Roberts notes that this is the core reasoning behind common advice: “When people say don't block the event loop, this is exactly what they're talking about. They're saying don't put shitty slow code on the stack because when you do that the browser can't do what it needs to do, create a nice fluid UI.”

He concludes the segment by warning that blocking the stack is not the only way to ruin UI performance. Inefficient event listeners, such as unthrottled DOM `scroll` handlers, fire continuously—often every 16 milliseconds. If a heavy callback is attached directly to window scrolling, rapid scrolling floods the task queue with hundreds of callbacks that each take time to execute, creating a massive backlog that stalls responsiveness even when individual execution frames appear brief.

- Screen repainting targets roughly 16.6 ms intervals (60 fps), which is the fastest the browser repaints if unconstrained. - The render queue takes precedence over standard callbacks once the call stack empties. - Intensive operations such as image processing or excessive DOM animations become sluggish whenever code monopolies the call stack or over-saturates the task queue.

[25:35](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1535s) Debouncing Callbacks and Under the Hood of the Visualization Tool

Philip Roberts wraps up the practical implications of event queue congestion by addressing high-frequency events like scrolling. When an application floods the task queue with rapid callbacks, it can completely choke responsiveness and stutter rendering. Roberts explains that developers can mitigate this by debouncing: “all these callbacks, there's way you can debounce that to basically say okay, we're going to queue up all those events, but let's do the slow work every few seconds or until the user stops scrolling for some amount of time I think that's basically it.”

He then shifts behind the scenes to briefly address how his interactive visualization tool—which runs user-submitted JavaScript live on screen—actually operates under the hood. Acknowledging that it could easily be an entirely separate presentation, Roberts pulls back the curtain on the hack: “There's a whole other talk in how the hell this works.” The code provided in the simulator runs at runtime, but to make the execution visible to the human eye, it passes through Esprima, an open-source JavaScript parser. Roberts programmatically instruments the code by inserting artificial delay loops into the abstract syntax tree—injecting a half-second `while` loop at each step to produce a slow-motion playback effect. The instrumented code is then shipped off to execute inside a Web Worker, allowing the main thread to capture and render the step-by-step movement of functions moving between the call stack, Web APIs, task queue, and event loop.

Closing his presentation, Roberts expresses his excitement about the visualization architecture, inviting anyone interested to come talk with him afterward because he thinks “it's kind of neat,” before thanking the conference audience to enthusiastic applause.

```mermaid flowchart LR accTitle: Loupe Visualizer Runtime Architecture accDescr: Flow diagram showing how Philip Roberts instruments and slows down code execution using Esprima and Web Workers to drive the UI visualization.

subgraph Instrumentation ["Code Instrumentation"] Code["Input Code"] --> Parser["Esprima Parser"] Parser --> Inject["Inject 0.5s While Loops"] end

subgraph Execution ["Execution & Display"] Inject --> Worker["Web Worker Runtime"] Worker --> UI["Visualizer UI\n(Stack, Web APIs, Task Queue)"] end ```

- **Debouncing:** Queues up frequent stream events (such as window scroll listeners) but delays expensive computations until a specified interval has elapsed or user activity pauses. - **Parsing with Esprima:** Uses the Esprima JavaScript parser to analyze the source code and insert artificial half-second blocking delays so the execution steps can be observed in slow motion. - **Web Worker isolation:** Offloads the execution of the slowed-down code to a background Web Worker, allowing the browser's main thread to safely visualize the runtime state.

## Notable quotes > [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) The event loops job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it. — Philip Roberts

## Mentioned &yet, Esprima, Google Chrome, JSConf EU, Loupe, Node.js, Philip Roberts, Ruby, Scotland JS, V8

## Video description JavaScript programmers like to use words like, “event-loop”, “non-blocking”, “callback”, “asynchronous”, “single-threaded” and “concurrency”.

We say things like “don’t block the event loop”, “make sure your code runs at 60 frames-per-second”, “well of course, it won’t work, that function is an asynchronous callback!”

If you’re anything like me, you nod and agree, as if it’s all obvious, even though you don’t actually know what the words mean; and yet, finding good explanations of how JavaScript actually works isn’t all that easy, so let’s learn!

With some handy visualisations, and fun hacks, let’s get an intuitive understanding of what happens when JavaScript runs.

Transcript: http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html

License: For reuse of this video under a more permissive license please get in touch with us. The speakers retain the copyright for their performances.

[Source](https://www.youtube.com/watch?v=8aGhZQkoFbQ)

中文简报

Roberts解析浏览器Web API如何在Chrome V8之外实现并发 由于JavaScript引擎严格在单调用栈上执行,开发者必须通过浏览器Web API分流耗时任务,以防止同步代码阻塞渲染循环。 Roberts通过将仅包含单线程调用栈和内存堆的V8引擎,与包含Web API、任务队列和事件循环的外部浏览器架构区分开来,阐明了JavaScript的执行模型。他展示了长时间运行的同步代码如何独占调用栈并卡死UI重绘,并将其与分流任务以保持主线程畅通的异步API进行了对比。事件循环充当协调器,只有在调用栈完全清空后,才会将任务队列中已完成的回调压入栈中。借助其定制的交互式可视化工具Loupe,Roberts阐释了`setTimeout(..., 0)`、16.6毫秒浏览器渲染周期以及对高频事件进行防抖以维持流畅性能的必要性等概念。 核心JavaScript运行时(例如V8)仅包含一个调用栈和一个内存堆;像setTimeout、DOM操作和XMLHttpRequest等关键的异步API均由浏览器环境作为Web API从外部提供。 (2:40) 事件循环充当任务队列与调用栈之间的协调器,只有在调用栈完全清空后,才会将等待中的回调压入栈中。 (12:19) 像setTimeout这样的定时器指定的是最短执行时间而非保证执行时间,这意味着零毫秒定时器只是将执行推迟到当前同步执行完成之后。 (14:55) 浏览器渲染以16.6毫秒(60 fps)为目标间隔,且优先级高于任务队列回调,但同步阻塞操作会彻底阻止重绘,从而导致UI卡死。 (22:15)

Reading summary

Philip Roberts explains that JavaScript's apparent concurrency relies not on the single-threaded V8 engine alone, but on coordination between the call stack, browser-provided Web APIs, the task queue, and the event loop. The Engine Versus the Surrounding Environment (2:40) A JavaScript engine like Google Chrome's V8 contains only a memory heap for allocation and a single call stack for tracking execution frames. Primitives commonly associated with the language—such as setTimeout, DOM manipulation APIs, and XMLHttpRequest—do not exist inside the V8 codebase. Instead, JavaScript execution is single-threaded, operating on a Last-In, First-Out stack that can execute only one piece of code at a time. Concurrency is introduced by the broader browser environment. Web APIs effectively operate as background threads to handle timers, network requests, and user interactions. In Node.js, C++ APIs fulfill this same role under the hood, allowing operations to proceed outside the primary execution thread. Event Loop and Task Queue Coordination (12:19) When an asynchronous API call completes, the browser environment cannot force the resulting callback directly onto the active call stack without interrupting running code. Instead, completed Web API tasks place their callbacks into a task queue. The event loop continuously monitors both the call stack and the task queue, pushing the first queued callback onto the stack only after the stack becomes entirely empty. This mechanism explains the behavior of patterns like setTimeout with a zero-millisecond delay. Even when a timer expires immediately, its callback must sit in the task queue until ongoing synchronous code clears from the stack. Consequently, setTimeout specifications define a minimum delay before execution rather than a guaranteed execution time. UI Freezes and Render Loop Priorities (22:15) Because JavaScript operates on a single call stack, slow or heavy synchronous code blocks the entire browser interface. While the stack is occupied, the browser cannot run other scripts, process user clicks, or perform visual repaints. Roberts demonstrates that keeping synchronous loops running causes the browser window to lock up until the stack completely empties. The browser attempts to repaint the screen roughly every 16.6 milliseconds to sustain a 60-frame-per-second display. The rendering pipeline receives higher priority than the task queue once the call stack is empty, but it still cannot intervene while synchronous execution is underway. Refactoring heavy processing into asynchronous chunks yields the stack back to the browser, allowing the render loop to update the interface smoothly between tasks. Queue Congestion and Event Throttling (25:35) A responsive interface can be degraded without long synchronous functions if the task queue is flooded with rapid callbacks. High-frequency user interactions, such as unthrottled DOM scroll listeners firing continuously, rapidly append tasks to the queue faster than the event loop can clear them. Roberts recommends techniques like debouncing to address this bottleneck. Debouncing consolidates streams of frequent events, ensuring computationally expensive tasks execute only after activity pauses or at spaced intervals, preventing task queue congestion from causing frame drops. To make these invisible runtime behaviors observable, Roberts built the Loupe visualization tool by parsing JavaScript with Esprima, injecting artificial half-second delay loops into the abstract syntax tree, and running the instrumented code inside a Web Worker so the main thread could render stack, API, and queue transitions in slow motion.

精炼阅读摘要

Philip Roberts在JSConf EU演讲中指出,JavaScript引擎本身虽是单线程执行环境,但浏览器通过结合Web API、任务队列与事件循环机制实现了异步并发。他强调,理解这一调度链路对于避免主线程调用栈阻塞及维持流畅的用户界面渲染至关重要。 运行时分工与浏览器协作体系 (2:40) Roberts指出,像Google Chrome的V8这类JavaScript引擎,其内部核心仅包含负责内存分配的堆(Memory Heap)和维护执行帧的调用栈(Call Stack),源码中并不包含setTimeout、DOM操作或XMLHttpRequest等常见功能。JavaScript作为单线程运行时,其调用栈采用后进先出(LIFO)结构,同一时刻只能执行一段代码,无限递归等异常会导致栈溢出并被浏览器强制终止。真正的并发能力源于浏览器整体环境:浏览器提供了独立的Web API线程来承载网络请求、计时器与事件监听,使耗时操作能够脱离JavaScript单线程环境在后台运转,而Node.js中则是通过C++ API实现了对底层线程管理的封装。 事件循环运转与异步调度本质 (12:19) 当Web API处理完后台任务(例如5秒定时器倒计时结束或接收到网络响应)后,不能随意将回调插入正在执行的代码中,而是将其推入任务队列(Task / Callback Queue)。事件循环的核心职责在于持续监控调用栈与任务队列的状态:只要调用栈尚未清空,排队的回调就必须等待;一旦调用栈归零,事件循环便会取出队列头部的回调推入调用栈执行。这一机制解释了setTimeout(fn, 0)并非立即同步执行,而是将任务推入队列以让出当前调用栈,待后续所有同步脚本执行完毕后才触发。因此,定时器设置的毫秒数仅代表回调进入执行状态的最小延迟时间,而非确定性的精准执行时间。 界面渲染瓶颈与队列过载风险 (22:15) 浏览器在无阻塞状态下的目标是以约16.6毫秒的间隔重绘屏幕,以维持60帧每秒的流畅界面体验。然而,只要调用栈被同步的大循环或重量级任务长期占据,浏览器便无法执行重新渲染,也无法响应用户点击或文本选择,导致整个页面冻结。当调用栈清空时,渲染任务的优先级高于普通回调队列,因此通过异步分片(如拆解数组处理循环)交出调用栈,能让浏览器插空完成渲染。此外,UI卡顿不仅由长时间占用栈引起,若对高频触发的滚动(scroll)等DOM事件未作防抖(debounce)处理,任务队列会在极短时间内被数百个排队回调塞满,同样会形成积压进而破坏应用响应性能。 可视化工具Loupe的实现机制 (25:35) 为了向开发者直观呈现这一抽象流程,Roberts开发了名为Loupe的在线可视化工具。该工具通过Esprima语法分析器解析输入的JavaScript代码,在抽象语法树的每个步骤中注入约0.5秒的等待循环以生成慢动作效果。随后,被插桩的代码被置于Web Worker中独立运行,主线程则实时捕获状态,动态渲染函数在调用栈、Web API容器、任务队列以及事件循环之间流转的完整生命周期。 Roberts总结认为,编写高性能前端代码的关键在于不把耗时的慢速代码堆死在调用栈上,并对高频事件合理节流防抖,从而为浏览器渲染和事件循环调度留出处理空间;他在演讲结束时未对其他引擎或微任务队列等更复杂的运行规范做进一步展开。

Detailed notes — zh

## Background 该视频由 JSConf 官方频道于 2014 年 10 月 9 日发布在 YouTube 上,记录了 Philip Roberts 在 JSConf EU 大会上的演讲。页面简介提到,JavaScript 开发者经常挂在嘴边的“事件循环”、“非阻塞”与“单线程”等术语往往让人似懂非懂,本场演讲旨在通过直观的可视化演示与趣味黑客技巧,帮助大家真正建立对 JavaScript 运行机制的直观理解。 ## TL;DR 演讲深入剖析了 JavaScript 作为单线程语言如何在浏览器中实现非阻塞并发的核心奥秘。Philip Roberts 澄清了 V8 引擎仅包含调用栈与内存堆的事实,指出 DOM、AJAX 与 setTimeout 等异步能力实则来自宿主环境的 Web APIs。通过自行开发的可视化工具 Loupe,他生动演示了任务队列与事件循环的协作流程:只有当主调用栈完全清空时,事件循环才会将排队的回调推入执行。最后,演讲探讨了阻塞调用栈对 60 FPS 渲染管线的破坏性影响,并给出了拆分异步长任务与利用防抖(Debounce)优化高频事件的实用建议。 ## Atmosphere & style 整场演讲幽默自嘲、充满活力且启发性极强。Philip Roberts 巧妙地以自身作为资深工程师却对底层概念一知半解的“坦白”切入,瞬间拉近了与台下开发者的心理距离。随着他演示自制可视化工具 Loupe 的动态交互,复杂抽象的底层调度变得极度具象且充满趣味,现场伴随着会心的笑声与豁然开朗的赞叹,给观众带来一种穿透技术黑盒、恍然大悟的爽快感。 ## Bulletin - [01:26](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=86s) JavaScript 引擎(如 V8)本身仅包含调用栈与内存堆,并不包含 `setTimeout`、DOM API 或事件循环,这些机制均由浏览器宿主环境的 Web APIs 与外部调度系统提供。 - [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) 事件循环的核心职责十分单一:持续监控调用栈,仅在调用栈完全清空时,才会将任务队列中的首个回调函数压入栈中执行。 - [14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) `setTimeout(fn, delay)` 设定的时间并非绝对执行时刻,而仅代表回调进入任务队列的最小等待延迟,`setTimeout(fn, 0)` 的本质是推迟执行直到当前主栈完全清空。 - [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) 浏览器渲染机制约每 16.6 毫秒(60 FPS)请求一次重绘,但无法强行抢占正在运行的 JavaScript 栈帧;任何长耗时同步任务都会直接导致页面掉帧与交互冻结。 ## Key takeaways - [04:16](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=256s) JavaScript 运行时是单线程的,仅拥有一个调用栈,同一时间只能执行一段代码,进入函数入栈、返回出栈,无限递归会导致调用栈溢出(爆栈)。 - [01:26](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=86s) V8 引擎源码中不存在 `setTimeout`、AJAX 或 DOM 操作代码,异步并发能力完全来自浏览器提供的外部 Web APIs(在 Node.js 中对应 C++ APIs)。 - [07:18](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=438s) 长时间占用调用栈的慢速同步代码(如复杂的 `while` 循环)会彻底阻塞浏览器,导致页面失去响应、交互搁置且无法重新渲染。 - [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) Web APIs 运行在后台独立线程中处理计时或网络请求,并在完成后将对应的回调推入任务队列,绝对无法直接插手或篡改调用栈。 - [14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) 事件循环在调用栈变空之前绝不会从任务队列中调度任何任务,因此所有同步代码永远优先于排队的回调函数执行完。 - [14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) `setTimeout(..., 0)` 并不能实现即时运行,而是作为一种代码延迟执行技巧,确保某些逻辑在调用栈彻底出栈后再执行。 - [20:16](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1216s) 回调函数存在同步与异步之分:`Array.prototype.forEach` 是完全同步执行并占满调用栈的,而通过定时器拆分的异步回调才会进入任务队列分批调度。 - [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) 浏览器的界面渲染拥有比普通任务队列更高的优先级,但它同样必须排队等待调用栈清空后才能执行界面绘制。 - [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) 高频触发的 DOM 事件(如滚动 `scroll`)如果未做防抖或节流处理,会导致大量回调瞬间淹没任务队列,形成严重的渲染排队卡顿。 - [25:58](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1558s) 可视化演示工具 Loupe 借助 Esprima 语法解析器在代码关键节点注入耗时 `while` 循环以实现慢动作,并放入 Web Worker 独立运行避免冻结主界面。 ## Detailed walkthrough ### [00:01](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1s) 演讲开场与一个困惑已久的疑问 主持人向大家介绍菲利普·罗伯茨(Phillip Roberts),他任职于专门从事实时开发的小型咨询公司 &yet(AndYet),来自苏格兰爱丁堡。菲利普登台后,看到分会场座无虚席感到非常兴奋,并打趣地让台下所有人跟他一起伸个懒腰,好让自己看起来不那么奇怪。 菲利普随后直奔主题,谈起这次演讲的核心命题:“事件循环(Event Loop)到底是个什么鬼东西?”他坦言,大约在 18 个月前,自己虽然已经是一名拿薪水的全职专业 JavaScript 开发者,但内心深处却产生了一个疑问:JavaScript 究竟是如何运转的?在当时,他对底层的很多概念其实一知半解。他常听人提起 V8、Chrome Runtime 之类的词汇,却根本不知道它们具体负责什么;他也知道 JavaScript 是“单线程”的,也一直在写回调函数,但他搞不懂回调函数到底是怎么运行的。这促使他开启了一段阅读、研究并在浏览器中动手实验的探索之旅。 ### [01:26](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=86s) 寻找答案的死胡同与完整的浏览器架构 菲利普幽默地模拟了自己当时与 JavaScript 的对话。当他问 JavaScript“你到底是什么”时,得到的教科书回答是:“我是单线程单并发语言,我有一个调用栈(Call Stack)、一个事件循环(Event Loop)、一个回调队列(Callback Queue)以及一些其他 API”。由于菲利普并没有计算机科学学位,这些术语听起来既抽象又令人困惑。于是他转向 Chrome 的 JavaScript 引擎 V8 寻求答案,以为能从中找到所有这些组件,结果翻看 V8 源码时才发现,V8 基本上只有调用栈和用于内存分配的堆(Memory Heap)。关于事件循环、回调队列或者其他 API,V8 里根本不存在。 更让他震惊的是,当他在 V8 源码中查找日常开发中最常见的异步要素(如 `setTimeout`、DOM 操作或 HTTP 请求)时,竟完全找不到这些东西的踪影。正如菲利普所总结的:“It's first thing you use when you start thinking about async stuff and it's not in the V8 source.” 经过长达 18 个月的摸索,他终于拼凑出了全局图景:V8 等运行时仅仅是整个生态的一部分,浏览器还提供了额外的 Web API(包括 DOM、AJAX、计时器等),而在它们之间穿针引线的,正是看似神秘的事件循环和任务/回调队列。 ```mermaid flowchart TD accTitle: 浏览器环境完整架构图 accDescr: 展示 JavaScript 运行时、Web API、回调队列与事件循环之间的协作关系 subgraph Runtime["JavaScript 运行时 (例如 V8)"] Heap["内存堆 (Heap)<br/>内存分配"] Stack["调用栈 (Call Stack)<br/>执行帧 / 单线程"] end subgraph Browser["浏览器宿主环境"] WebAPIs["Web APIs<br/>DOM, AJAX, setTimeout..."] Queue["回调队列 (Callback Queue)<br/>onClick, onLoad, onDone..."] EventLoop["事件循环 (Event Loop)"] end Stack -.->|"调用 Web API"| WebAPIs WebAPIs -.->|"任务完成推入"| Queue Queue -.->|"被调度送回"| EventLoop EventLoop -.->|"栈清空时压入"| Stack ``` ### [04:16](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=256s) 单线程与调用栈(Call Stack)的运作机制 菲利普从最基础的机制讲起,深入剖析了 JavaScript 的单线程本质。所谓单线程运行时,意味着它只有一个调用栈,同一时间只能做一件事。为了具象化这个概念,他用一段简单的代码进行演示:一个将两个数字相乘的 `multiply` 函数、一个调用乘法计算平方的 `square` 函数,以及一个调用 `square` 并打印结果的 `printSquare` 函数。 调用栈本质上是一种记录程序当前运行位置的数据结构:进入一个函数就将其压入栈顶(push),从函数返回时就从栈顶弹出(pop)。当文件开始执行时,首先入栈的是代表整个脚本的 `main` 函数;随后执行到 `printSquare(4)`,将其推入栈中;在 `printSquare` 内部调用了 `square(4)`,再次入栈;`square` 又调用了 `multiply(4, 4)` 进栈;`multiply` 计算完成后执行 `return`,该函数出栈;接着 `square` 返回出栈;随后调用栈执行 `console.log` 输出结果并完成;最后 `printSquare` 运行完毕并随同主文件退出栈,整个调用栈归零。 菲利普指出,哪怕开发者以前没有刻意关注过调用栈,在平时调试浏览器代码时也肯定接触过。例如,当一个嵌套调用的函数抛出未捕获的错误时,Chrome 控制台打印出的调用栈轨迹(Stack Trace)清晰展示了崩溃时栈内的逐层状态:`Foo`、`Bar`、`Baz` 以及最外层的匿名主函数 `main`。类似地,如果编写一个无限递归调用自身的 `foo` 函数,调用栈就会被不断塞满,直到 Chrome 抛出“RangeError: Maximum call stack size exceeded”(即所谓的爆栈 Blow the stack),自动终止程序以防止崩溃。 - **调用栈的作用**:记录当前代码执行到了哪一步。 - **基本操作行为**:进函数 push 入栈,遇 return 或执行完毕 pop 出栈。 - **调用栈溢出(Blowing the stack)**:递归无终止条件导致超出栈容量,浏览器通常在达到约 16,000 次调用时主动报错中断。 ### [07:18](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=438s) 为什么代码会阻塞?同步带来的浏览器灾难 既然 JavaScript 只有一个线程和一个调用栈,那么当操作变慢时会发生什么?菲利普讨论了阻塞(Blocking)的概念。虽然技术上对什么是阻塞没有严格的唯一定义,但简而言之,任何慢速的代码都会造成阻塞。`console.log` 不慢,但执行一个从 1 数到 100 亿的 `while` 循环就会慢,网络请求、图像加载同样十分缓慢。当这些耗时很长的操作霸占在调用栈上时,就是所谓的阻塞行为。 为了直观展示这一点,菲利普假设如果 AJAX 网络请求是同步的(比如伪代码中的 `getSynchronous`),会发生什么局面:程序调用同步请求后必须干等(Wait),直到请求完成才能执行下一步;随后再发一个请求,继续干等。如果网络出现故障,请求永远无法完成,程序就彻底被挂起。 随后菲利普在屏幕上打开 Chrome 进行了现场演示。由于在浏览器中模拟同步请求比较繁琐,他用一个空转 5 秒钟的庞大 `while` 循环伪装成同步网络请求。当他点击“Run”按钮执行这段代码时,灾难发生了: 1. 页面完全失去响应,甚至 Run 按钮的点击重绘状态都无法完成渲染。 2. 开发者在界面上的任何点击操作均被搁置,浏览器陷入彻底的“假死”冻结状态。 3. 菲利普对此生动地描述道:“The browser is blocked, it's stuck, it can't do anything until those requests complete.” 4. 直到 5 秒的同步执行彻底结束,之前积压的用户交互操作才一口气爆发出来。 菲利普强调,这正是单线程在浏览器端面临的最大痛点:只要调用栈上有代码在执行,浏览器就无法重新渲染界面,也无法响应任何其他事件。如果想让用户获得丝滑流畅的 UI 交互,就绝对不能阻塞调用栈。 ### [10:22](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=622s) 异步回调与并发魔法的揭秘 为了解决调用栈阻塞的问题,JavaScript 给出的核心方案是异步回调(Asynchronous Callbacks)。无论在浏览器端还是 Node.js 环境中,绝大部分底层任务都被设计成异步的:调用某个函数,传入一个回调函数,稍后再去执行它。 菲利普给出了一个极其经典的测试样例: 1. `console.log('hi')` 2. `setTimeout(function cb() { console.log('there'); }, 5000)` 3. `console.log('JSConfEU')` 按照调用栈的直觉理解,先打印出 `hi`,然后遇到 `setTimeout`。我们知道传给它的回调不会立刻执行,而是要在 5 秒后触发,但它显然无法一直停留在调用栈上占位。神奇的是,`setTimeout` 瞬间就从调用栈上消失了,接着代码毫无停滞地执行并打印出 `JSConfEU`,调用栈被完全清空。而在整整 5 秒钟之后,回调函数仿佛被施了魔法一样凭空回到了调用栈中,打印出 `there`。 这种“消失又出现”的现象究竟是如何做到的?菲利普在此揭开了演讲的核心真相:“Right, so I've been kind of partially lying to you and telling you that JavaScript can only do one thing at one time.” 他解释道,虽然 JavaScript 运行时(如 V8)本身确实同一时刻只能做一件事,既不能在跑业务逻辑的同时发网络请求,也不能在执行代码的同时做定时等待,但浏览器本身绝不仅仅只有 JavaScript 运行时。浏览器提供的 Web API 具备真正的多线程并发能力,而事件循环正是协调这些组件协同工作的关键纽带。 ### [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) 浏览器的 Web APIs、任务队列与事件循环的核心职责 浏览器并不仅包含 V8 这样的单一 JavaScript 运行时,它还额外提供了许多被称为 Web APIs 的底层能力。这些 Web APIs 本质上是浏览器在后台开启的线程,开发者只需在 JavaScript 中调用它们,并发处理(concurrency)便由此介入。Philip Roberts 指出,如果你是后端开发者,Node.js 的内部架构图与此基本相同,只不过浏览器端的 Web APIs 在 Node 端被替换成了 C++ APIs,多线程的复杂性全部被封装在了 C++ 背后。 当 JavaScript 执行 `setTimeout(callback, delay)` 时,该 API 并不存在于 V8 引擎内部,而是浏览器运行时提供的外部扩展。浏览器会为此接管倒计时任务。此时,调用栈中的 `setTimeout` 本身已经执行完毕并立即出栈(Pop),主线程得以继续向下执行后续代码(例如打印 `"JSConfEU"`)。当五秒倒计时结束后,Web API 并不能直接把回调函数扔回调用栈里,否则这些代码就会毫无预兆地随机插入到正在执行的主流程中。为了保证执行的可预测性,Web API 在完成任务后会将回调函数推入“任务队列”(Task Queue,亦称 Callback Queue)。 至此,演讲的主角——事件循环(Event Loop)登场。Philip Roberts 说明:“The event loops job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it.”(事件循环的工作就是看着调用栈,同时看着任务队列;一旦调用栈变空,它就取出任务队列中的第一项并推入调用栈,从而让它执行)。只有当主栈完全清空后,事件循环才会将队列中的回调推回 V8 的执行栈中,回调里的 `console.log("there")` 才能最终完成打印。 ```mermaid flowchart LR accTitle: JavaScript 运行时与事件循环架构 accDescr: 展示调用栈、Web APIs、任务队列以及事件循环之间协同工作的流程图。 subgraph Runtime["JavaScript 运行时 (V8)"] Stack["调用栈 (Call Stack)"] end subgraph Browser["浏览器环境"] WebAPIs["Web APIs\n(Timer / DOM / AJAX)"] Queue["任务队列 (Task Queue)"] end Stack -->|"调用异步 API"| WebAPIs WebAPIs -->|"计时/网络完成"| Queue Queue -.->|"栈清空时出队推入"| Loop(("事件循环\nEvent Loop")) Loop -.->|"推入执行"| Stack ``` - `setTimeout` 并非 V8 源码的一部分,而是由运行环境(浏览器或 Node.js)提供的外部 API。 - Web APIs 在后台独立线程处理计时或网络请求,倒计时结束后仅将回调注入任务队列,不能直接篡改调用栈。 - 事件循环的角色极其纯粹且单一:不断检测调用栈是否为空,若为空且队列中有排队任务,便将首个回调压入调用栈。 --- ### [14:55](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=895s) 解密 `setTimeout(fn, 0)` 与通用异步 API 机制 初学异步 JavaScript 时,开发者常常会遇到要求编写 `setTimeout(fn, 0)` 的古怪建议。很多人第一反应都是大惑不解:既然要等待 0 毫秒立即执行,为何还要特意包一层 `setTimeout`?Philip Roberts 坦言,自己第一次见到这种写法时也是一头雾水:“The reason is, generally, if you're trying to defer something until the stack is clear.”(其原因通常在于,你试图将某些操作推迟到调用栈完全清空之后再执行)。 即使延迟设定为 0 毫秒,浏览器的 Web API 也会立即将回调放入任务队列。然而,由于事件循环必须严格等待当前的调用栈全部清空,后续的同步代码(如打印 `"hi"`、`"JSConfEU"`)依然会优先在主栈中跑完。直到主线程代码全部出栈,事件循环才介入并将排队的回调推上调用栈。 所有异步 Web APIs 都遵循完全相同的模式。以常见的 AJAX 请求为例,发起请求时传入回调与目标 URL,具体执行网络传输的代码并不在 JavaScript 运行时里,而是在浏览器的 Web API 线程池中转动。在此期间,主调用栈无需等待网络返回,可以顺畅执行后续代码;甚至即使 XHR 请求失败或永远没有响应,主栈也不会挂起。一旦请求成功返回,回调即被推入队列,等待事件循环在空闲时拉取执行。 - `setTimeout(..., 0)` 并不能实现真正的“即刻运行”,而是实现代码执行的延迟(Defer),强制其排在当前调用栈全部出栈之后。 - 异步 AJAX 请求将繁重的 I/O 等待卸载到浏览器宿主环境,使单线程 JavaScript 能够持续响应。 --- ### [16:58](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1018s) 演示工具 Loupe 的诞生与真实交互队列观察 为了直观展示复杂的执行流程,Philip Roberts 原本在 Keynote 幻灯片里手工制作了大约 500 个极其繁琐的动画步骤。但在苏格兰 JS 大会(Scotland JS)后,幻灯片坏了一半,重新在 Keynote 里排版调整极其痛苦。于是,他选择了一条幽默的“偷懒”捷径——索性自己动手写了一套名为 Loupe 的可视化工具,在运行时实时模拟 JavaScript 运行时、Web APIs、任务队列与事件循环的动态流转。 在 Loupe 中运行包含 DOM 点击监听(`addEventListener`)和 `setTimeout` 的代码,可以清晰看到调用栈如何迅速消费同步逻辑,并将事件监听注册到 Web APIs 区块。当用户在页面上点击按钮时,Web API 立即触发并将对应的点击回调推入任务队列。如果快速连续点击按钮 100 次,这些点击事件并不会立刻穿透到 JavaScript 引擎执行,而是全数堆积在任务队列中排队。主栈在处理完当前指令后,由事件循环逐个将点击回调拉入栈中处理。 这进一步揭示了计时器的真实语义。如果连续调用 4 次延迟为 1 秒的 `setTimeout` 并紧随一个同步的打印,当第四个定时器在 Web API 中到期并排入任务队列时,由于前面有正在等待或执行的回调,第四个回调实际被调用的时间必然远远超出设定的 1 秒。Philip Roberts 强调指出:“...it's not a guaranteed time to execution, it's a minimum time to execution, just like setTimeout zero doesn't run the code immediately it runs the code next‑ish, sometime, right?”(……它并不是保证执行的时间点,而是一个最小执行延迟;就像 `setTimeout` 设为 0 并不会马上执行代码,而是在接下来的某个时刻执行,对吧?)。 - Loupe 是作者为了规避 Keynote 繁复动画制作而开发的可视化调试与教学工具。 - 用户交互(如点击事件)在宿主层触发后,必须转化为队列中的任务,按先进先出顺序等待调度。 - `setTimeout(fn, delay)` 中的 `delay` 仅代表回调进入任务队列的最小等待时间,绝非精确的执行时刻保证。 --- ### [20:16](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1216s) 同步回调与异步回调的辨析 在日常技术讨论中,“回调”(Callback)一词经常引发概念混淆。Philip Roberts 澄清,回调在语境中通常包含两种含义:广义上指代“任何被另一个函数调用的函数”;而在异步编程的严格语境下,则特指“未来会被推入回调队列的异步函数”。 以数组内置的 `forEach` 方法为例,虽然它接受一个函数参数并习惯被称为回调,但它完全是同步执行的,每一个迭代调用都直接堆叠在当前调用栈中,牢牢霸占主线程,直到整个数组遍历结束才让出。如果手动封装一个利用 `setTimeout(..., 0)` 实现的异步版 `asyncForEach`,行为则截然不同:它在循环中迅速将每一次处理包装为异步任务投递到 Web APIs 和任务队列,从而迅速清空当前的同步栈,使后续的同步输出能够立刻执行,之后再由事件循环逐个消化各元素的回调。 ```javascript // 同步 forEach:回调直接在当前调用栈中执行,阻塞主线程 [1, 2, 3, 4].forEach(function(i) { console.log(i); }); // 异步 forEach:利用异步 API 将每次执行推迟到任务队列 function asyncForEach(array, cb) { array.forEach(function(item) { setTimeout(function() { cb(item); }, 0); }); } ``` - 同步回调(如原生 `Array.prototype.forEach`)同步占用调用栈,栈未执行完前外部代码完全停滞。 - 异步回调借助调度机制解耦,各迭代项作为独立任务排入任务队列,分批次被调用栈执行。 --- ### [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) 浏览器渲染管线、掉帧与“不要阻塞事件循环” JavaScript 的单线程机制对浏览器的渲染管线(Render Pipeline)有着决定性的制约。理想状态下,为了呈现流畅的用户界面,浏览器期望以每秒 60 帧(60 FPS)的速率重绘屏幕,也就是大约每 16.6 毫秒就要执行一次重绘(Repaint/Render)。然而,只要当前 JavaScript 调用栈中有任何代码正在运行,浏览器就无法插入渲染流程。渲染流程在行为上类似于一个拥有更高优先级的特殊回调:它大约每 16 毫秒就会发起一次重绘请求,但前提是必须静候调用栈彻底清空。 Philip Roberts 现场展示了他在演示工具中加入的重绘模拟机制。当主线程运行耗时严重的同步循环处理时,渲染队列被彻底锁死。在重绘受阻的这段时间里,用户在屏幕上无法选中文本,点击按钮没有任何视觉反馈,页面呈现死锁状态。相反,如果将耗时计算拆解为异步批处理,虽然在推入定时器时会有极短暂的微小占用,但在每次处理元素之间,调用栈都会清空,从而让浏览器的渲染机制有机会穿插进来完成界面绘制。 谈及社区常说的性能箴言,Philip Roberts 总结道:“They're saying don't put shitty slow code on the stack because when you do that the browser can't do what it needs to do, create a nice fluid UI.”(他们是在说,不要把又烂又慢的代码往栈上扔,因为一旦你这么做了,浏览器就无法完成它该做的工作——打造一个丝滑流畅的用户界面)。不仅低效的同步运算会阻塞调用栈,不加节制的事件处理同样会拖垮系统。例如 DOM 中的滚动事件(`scroll`)在滑动时会高频触发(甚至达到每 16 毫秒触发一次)。如果在 `document.scroll` 的回调中绑定沉重复杂的动画或运算,虽然单次调用并没有长期占死栈空间,但海量的回调会像洪水一样瞬间淹没任务队列(Flooding the queue),造成灾难性的排队卡顿。 - 浏览器理想刷新频率为 60 FPS(每帧约 16.6ms),任何驻留在调用栈中的长任务都会直接推迟重绘并引发掉帧。 - 渲染调度虽然比普通任务队列中的异步回调拥有更高优先级,但依然无法强行抢占(Preempt)正在执行的 JavaScript 栈帧。 - 高频触发的事件(如滚动、手势等)如果未做防抖或节流,会导致任务队列泛滥积压,形成严重的界面滞后。 ### [25:35](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1535s) 防抖优化与事件循环的实际应用 在处理诸如滚动(scroll)事件等会高频触发的回调函数时,很容易导致调用栈和任务队列被严重阻塞,进而引发界面卡顿。Philip Roberts 指出,面对这种情况可以通过防抖(debounce)技术来进行性能优化。这种方案的核心思想在于对事件进行排队和合并控制,避免每一次触发都立即执行耗时的复杂计算,而是“每隔几秒钟执行一次慢速任务,或者直到用户停止滚动一段时间后再执行”。通过这种控制执行节奏的方式,可以保证页面响应灵敏,不会因为大量回调堆积而直接卡死浏览器的渲染通道。 ```mermaid flowchart TD accTitle: 事件防抖机制 accDescr: 展示高频触发事件如何通过防抖机制合并处理,以避免阻塞调用栈。 subgraph Trigger["高频触发阶段"] E1["滚动事件 1"] E2["滚动事件 2"] E3["滚动事件 N"] end subgraph Control["防抖控制层"] D["Debounce 调度"] end subgraph Exec["执行阶段"] W["执行耗时计算任务 / 渲染"] end E1 --> D E2 --> D E3 --> D D -->|"用户停止滚动后 / 间隔触发"| W ``` ### [25:58](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1558s) 可视化演示工具的实现幕后与结语 为了向观众清晰展示事件循环的内部运行机制,Philip Roberts 自制了一款专门的可视化工具(即著名的 Loupe)。在演讲接近尾声时,他笑着向大家揭秘了该工具在底层究竟是如何实现“慢动作”动画效果的。他坦言这套工具的实现细节非常丰富,直言:“There's a whole other talk in how the hell this works.” 这段代码之所以能够在页面上以极慢的节奏逐步呈现,是因为它在运行时被动态处理了:程序首先会通过 JavaScript 语法解析器 Esprima 对输入的代码进行解析和语法分析,随后在代码的关键节点之间硬编码插入一个耗时约半秒的大型 `while` 循环,从而人工为代码执行加上“慢动作放慢滤镜”。紧接着,代码会被派发到 Web Worker 中运行,并配合大量的前端交互与状态捕捉逻辑,实时还原调用栈、Web API、任务队列与事件循环之间的协同状态。Philip Roberts 表示自己对这个工具的实现机制感到非常兴奋,并热情邀请对技术细节感兴趣的参会者在演结束后与他线下交流。 - **解析与插桩**:利用 JavaScript AST 解析器 Esprima 解析用户编写的代码,并在代码执行路径中注入耗时约半秒的 `while` 循环来实现慢镜头执行效果。 - **后台隔离**:将经过处理的代码交由 Web Worker 独立执行,以此在不冻结主界面渲染的前提下实时监控运行状态并生成可视化视图。 ## Notable quotes > [12:19](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=739s) The event loops job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it. — Philip Roberts > [22:15](https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=1335s) They're saying don't put shitty slow code on the stack because when you do that the browser can't do what it needs to do, create a nice fluid UI. — Philip Roberts ## Mentioned Philip Roberts, JSConf EU, &yet, JavaScript, Chrome, V8, Node.js, Loupe, Keynote, Scotland JS, Esprima, Web Worker ## Video description JavaScript programmers like to use words like, “event-loop”, “non-blocking”, “callback”, “asynchronous”, “single-threaded” and “concurrency”. We say things like “don’t block the event loop”, “make sure your code runs at 60 frames-per-second”, “well of course, it won’t work, that function is an asynchronous callback!” If you’re anything like me, you nod and agree, as if it’s all obvious, even though you don’t actually know what the words mean; and yet, finding good explanations of how JavaScript actually works isn’t all that easy, so let’s learn! With some handy visualisations, and fun hacks, let’s get an intuitive understanding of what happens when JavaScript runs. Transcript: http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html License: For reuse of this video under a more permissive license please get in touch with us. The speakers retain the copyright for their performances. [Source](https://www.youtube.com/watch?v=8aGhZQkoFbQ)

Source quotes

[12:19] Philip Roberts: The event loops job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it. [22:15] Philip Roberts: They're saying don't put shitty slow code on the stack because when you do that the browser can't do what it needs to do, create a nice fluid UI.

Source quotes

[12:19] Philip Roberts: The event loops job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it.

Original transcript

[0:01] >> (Phillip Roberts) hello, come in and sit down. [0:04] So for the last session before the afternoon break, we have Phillip Roberts who works at [0:10] Andea and is here from London ‑‑ Scotland. [0:12] Edinbrough. [0:13] ‑‑ wow, ten second memory, he's going to talk about the vent loop. [0:17] If everyone could give Phillip a big brownedder round of applause. [0:18] >> Phillip Roberts: Okay hello everyone, thanks for coming to the side track, it's awesome [0:20] to see it packed out in here. [0:22] Can everyone give me a stretch. [0:24] I needed to stretch, so I look less weird. [0:31] I want to talk about the event loop and what the heck is the event loop, as in the event [0:37] loop inside JavaScript. [0:39] So first up, as he said I work for AndYet which is an awesome little Dev shop in the [0:46] US, look us up if you need help with real‑time stuff. [0:50] That's what we're good at. [0:52] So, about 18 months ago--I'm a paid professional JavaScript developer--I thought to myself [1:00] how does, like JavaScript actually work? [1:03] And I wasn't entirely sure. [1:05] I'd heard V8 as a term, chrome's Runtime didn't really know what that meant, what that did. [1:12] I'd heard things like single threaded, you know obviously I'm using callbacks. [1:16] How do callbacks work? [1:17] I started a journey of like reading and research and experimenting in the browser which basically [1:25] started like this. [1:26] ‑‑ I was kind of like JavaScript what are you. [1:30] I'm a single threaded single concurrent language ‑‑ right. [1:35] yeah, cool, I have a call stack, an event loop, a callback queue, and some other APIs [1:41] and stuff. [1:42] ‑‑ rite. [1:43] I did not do a computer science degree. [1:45] I mean, these words, they're words, so I heard about V8 and the various Runtimes and different [1:51] browsers so I looked to V8 do you have a call stack, an event loop, a callback queue, and [1:55] some other APIs and stuff, I have a call stack and a heap, I don't know what those other [2:00] things are, okay, interesting so basically 18 months passed. [2:09] And I think I get this. [2:12] (Laughing) and so, this is what I want to share with you today. [2:15] Hopefully this will be useful if you're relatively new to JavaScript, help you understand why [2:21] JavaScript is so weird when you compare it to other languages you might used why callbacks [2:26] are a thing, cause us hell but are required. [2:29] And if you're an experienced JavaScript developer hopefully give you some fresh insights how [2:35] the Runtime you're using works so you can think about it a little better. [2:40] So if we look at the JavaScript Runtime itself like V8 which is the Runtime inside Chrome. [2:51] This is a simplified view of what JavaScript Runtime is. [2:55] The heap, where memory allocation happens, and then there's the call stack, which is [3:01] where your stack frames are and all that kind of stuff, but, if you, like, clone the V8 [3:09] code base and grep for things like setTimeout or DOM or HTTP request, they're not in there, [3:17] they don't exist in V8, which was a surprise to me. [3:21] It's first thing you use when you start thinking about async stuff and it's not in the V8 source. [3:27] Hmm ... interesting. [3:29] So, over this 18 months of discovery I come to realize this is really, this is really [3:35] the bigger picture, this is what I'm hoping to get you on board with today and understand [3:40] what these pieces are, we have the V8 Runtime but then we have these things called web APIs [3:45] which are extra things that the browser provides. [3:47] DOM, AJAX, time out, things like that, we have this mythical event loop and the callback [3:54] queue. [3:55] I'm sure you've heard some of these terms before, but maybe you don't quite understand [3:59] how these pieces pull together. [4:01] So, I'm going to start from the beginning, some of this will be new, to words might be [4:06] new to people, other people will get this. [4:08] We're going to quickly move on from here, bear with me if this is obvious, I think for [4:13] a lot of people it's not. [4:16] So, JavaScript is a single threaded programming language, single threaded Runtime, it has [4:21] a single call stack. [4:23] And it can do one thing at a time, that's what a single thread means, the program can [4:28] run one piece of code at a time. [4:30] So, let's try and visualize that just to get our heads around what that mean, so if I have [4:35] some code like this on your left, we've got a few functions, a function multiplier which [4:40] multiplies two numbers, square which calls multiply with the same number twice, a function [4:46] which prints the square of a number of calling square and then calling console.log and then [4:51] at the bottom of our file we actually run print square, this code all good? [4:55] Make sense? [4:56] Cool. [4:57] So, if we run this, well, I should back up a step, so the call stack is basically ‑‑ [5:04] it's a data structure which records basically where in the program we are, if we step into [5:08] a function, we put something on to the stack, if we return from a function, we pop off the [5:12] top of the stack that's all the stack can do, ‑‑ so if you run this file, there's [5:18] kind of a main function, right, like the file itself, so, we push that on to the stack. [5:23] Then we have some function definitions, they're just like defining the state of the world, [5:28] and finally we got to print square, right, so print square is a function call, so we [5:32] push that on to the stack, and immediately inside print square, push on to the stack, [5:37] which calls multiply, now we have a return statement, we multiply A and B and we return, [5:43] when we return we pop something off the stack, so, pop, multiplier of the stack, returning [5:48] to square, return to print square, console.log, there's no return, it's implicit, because [5:57] we got to the end of the function, and we're done so that's like a visualization of the [6:01] call stalk, does that make sense? [6:05] (Yes, Phil) even if you haven't thought about the call stack before, you've come across [6:11] it when you've been doing browser‑side development, so if we have code like this, a function baz [6:18] which calls bar, which calls Foo, which throws an error if we run it in Chrome we see this. [6:26] And it prints the stack trace, right, the state of the stack when that error happened, [6:32] so, uncaught error oops Foo, bar, Baz, anonymous function, which is our main. [6:40] Equally, if you've heard the term like blowing the stack, this is an example of that. [6:46] Have a function foo which calls Foo , so what's going to happen ? We have a function main [6:52] which calls foo which calls foo, which calls foo, which calls foo, and ultimately chrome [7:00] says, you probably didn't mean to call foo 16,000 times recursively, I'll just kill things [7:07] for you and you can figure out where your bug lies, right. [7:10] So although I may be representing a new side of the call stack you have some sense of it [7:16] in your development practice already. [7:18] So, the big question then comes is like what happens when things are slow? [7:24] So, we talk about blocking and blocking behavior and blocking, there's no strict definition [7:31] of what is and didn't blocking, really it's just code that's slow. [7:35] So console.log isn't slow, doing a while loop from one to ten billion is slow, network requests [7:41] are slow. [7:42] Image requests are slow. [7:46] Things which are slow and on that stack are what are blocking means. [7:52] So heres a little example, so let's say we have, this is like a fake bit of code, getSynchronous, [7:59] right, like jQuery is like, AJAX request. [8:03] What would happen if those were synchronous requests, forget what we know about async [8:07] callbacks they're synchronous. [8:10] If we go through it like we have, we call getSync and then we wait, because then we're [8:16] doing network request, network is relative to computers, are slow, hopefully that network [8:21] requests completes, we can move on, wait, move on. [8:27] Wait, and, I mean, this network request might never finish, so ... yeah, I guess I'll go [8:37] home. [8:38] Finally those three, you know blocking behaviors complete and we can clear the stack, right. [8:43] So in a programming language is single threaded you're not using threads like say Ruby, that's [8:49] what happens, right, we make a network request, we have to just wait till it's done, because [8:54] we have no way of handling that. [8:56] Why is this actually a problem? [9:00] The problem is because we're running code in browsers. [9:04] So, let's you ‑‑ here we go, okay. [9:10] So this is just, this is Chrome, this is the code I just ran. [9:15] Browsers don't give us ‑‑ well they do give us synchronous AJAX request, I'm faking [9:20] this out with a big while loop, because it's synchronous, I basically while loop for five [9:26] seconds before continuing, so if I open up the console here. [9:33] We can see what happens, so with request foo.com, why this is happening, I can't do anything, [9:40] right, even the run button hasn't finished rerendering the fact that I clicked it. [9:44] The browser is blocked, it's stuck, it can't do anything until those requests complete. [9:51] And then all hell breaks loose because I did some stuff,it figured that out I'd done it, [9:55] it couldn't actually render it. [9:58] Couldn't do anything. [10:00] That's because if that call stack has things on it, and here it's got these yeah, it's [10:07] still going. [10:08] We've got the synchronous request, the browser can't do anything else. [10:12] It can't render, it can't run any other code, it's stuck. [10:16] Not ideal, right if we want people to have nice fluid UIs, we can't block the stack. [10:22] So, how do we handle this? [10:24] Well the simplest solution we're provided with is asynchronous callbacks, there's almost [10:29] no blocking functions in the browser, equally in node, they're all made asynchronous, which [10:36] basically means we run some code, give it a callback, and run that later, if you've [10:42] seen JavaScript you've seen asynchronous callbacks, what does this actually look like. [10:47] Simple example to remind people where we're at. [10:51] Code like this, console.log hi. [10:53] Write, we run the setTimeout, but that queue's the console log for future so we skip on to [11:00] JSConf and then five seconds later we log "there" right, make sense? [11:06] Happy. [11:07] Basically that's setTimeout is doing something. [11:09] So, asynchronous callbacks with regards to the stacks we saw before ... how does this [11:15] work? [11:16] Let's run the code. [11:18] Console.log hi. [11:21] setTimeout. [11:22] We know it doesn't run immediately, we know it's going to run in five seconds time, we [11:28] can't push it on to the stack, somehow it just disappears, we don't have like a way [11:34] of describing this yet, but we'll come to it. [11:37] We log JSConfEU, clear, five seconds later somehow magically "there" appears on the stack. [11:44] How does that happen? [11:48] And that's ‑‑ this is basically where the event loop comes in on concurrency. [11:53] Right, so I've been kind of partially lying do you and telling you that JavaScript can [11:58] only do one thing at one time. [12:00] That's true the JavaScript Runtime can only do one thing at one time. [12:03] It can't make an AJAX request while you're doing other code. [12:06] It can't do a setTimeout while you're doing another code. [12:09] The reason we can do things concurrently is that the browser is more than just the Runtime. [12:15] So, remember this diagram, the JavaScript Runtime can do one thing at a time, but the [12:19] browser gives us these other things, gives us these we shall APIs, these are effectively [12:23] threads, you can just make calls to, and those pieces of the browser are aware of this concurrency [12:31] kicks in. [12:33] If you're back end person this diagram looks basically identical for node, instead of web [12:38] APIs we have C++ APIs and the threading is being hidden from you by C++. [12:48] Now we have this picture let's see how this code runs in a more full picture of what a [12:53] browser looks like. [12:54] So, same as before, run code, console log hi, logs hi to the console, simple. [13:01] now we can see what happens when we call setTimeout. [13:05] We are ‑‑ we pass this callback function and a delay to the setTimeout call. [13:11] Now setTimeout is an API provided to us by the browser, it doesn't live in the V8 source, [13:17] it's extra stuff we get in that we're running the JavaScript run [13:22] time in. [13:25] The browser kicks off a timer for you. [13:27] And now it's going to handle the count down for you, right, so that means our setTimeout [13:33] call, itself is now complete, so we can pop off the stack. [13:37] “JSConfEU”, clear, so, now we've got this timer in the web API, which five seconds later [13:45] is going to complete. [13:46] Now the web API can't just start modifying your code, it can't chuck stuff onto the stack [13:52] when it's ready if it did it would appear randomly in the middle of your code so this [13:58] is where the task queue or callback queue kicks in. [14:00] Any of the web APIs pushes the callback on to the task queue when it's done. [14:07] Finally we get to the event loop, title of the talk, what the heck is the event loop [14:12] is like the simplest little piece in this whole equation, and it has one very simple [14:18] job. [14:19] The event loops job is to look at the stack and look at the task queue. [14:22] If the stack is empty it takes the first thing on the queue and pushes it on to the stack [14:26] which effectively run it. [14:29] So here we can see that now the stack is clear, there's a callback on the task queue, the [14:34] event loop runs, it says, oh, I get to do something, pushes the callback on to the stack. [14:39] Remember it's the stack is like JavaScript land, back inside V8, the callback appears [14:45] on the stack, run, console.log “there”, and we're done. [14:49] Does that make sense? [14:52] Everyone where me? [14:53] Awesome! [14:54] Okay. [14:55] So, now we can see how this works with probably one of the first encounters you would have [15:00] had with Async stuff which for some weird reason someone says says you have to call [15:04] setTimeout zero, ‑‑ okay, you want me to run the function in zero time? [15:11] Why would I wrap it in a setTimeout? [15:12] Like the first time you run across this, if you're like me,i see it doing something, but [15:17] I don't know why. [15:20] The reason is, generally, if you're trying to defer something until the stack is clear. [15:25] So we know looking at this, if you've written JavaScript, that we're going to see the same [15:29] result, we're going to see “hi” “JSConf”, and “there” is going to appear at the [15:33] end. [15:35] We can see how that happens. [15:38] The setTimeout zero, now it's going to complete immediately and push it on to the queue, remember [15:44] what I said about the event loop, it has to wait till the stack is clear before it can [15:47] push the callback on to the stack, so your stack is going to continue to run, console.log [15:53] “hi”, “JSConfEU” and clear, now the event loop can kick in and call your callback. [16:00] That's like an example of setTimeout zero, is deferring that execution of code, for whatever [16:07] reason to the end of the stack. [16:11] Or until stack is clear. [16:13] Okay. [16:14] So, all these web APIs work the same way, if we have AJAX request, we make an AJAX request [16:21] to the URL with a callback, works the same way, oops sorry, console log, “hi”, make [16:28] an AJAX request, the code for running that AJAX request does not live in JavaScript Runtime [16:32] but in the browser as a web API, so we spin it up with a callback in the URL, your code [16:40] can continue to run. [16:42] Until that XHR request completes, or it may never complete, it's okay, the stack can continue [16:47] to run, assuming it completes, gets pushed to the queue,picked up by the event loop and [16:52] it's run. [16:55] That's all that happens when an Async call happens. [16:58] Let's do a crazy complicated example, I hope this going to work, if you haven't realized [17:05] all this is in keynote there's like I don't know 500 animation steps in this whole deck. [17:12] (code blows up, flames animation) (Applause) J Whew ... no ... so ... interesting, we're [17:35] given a link. [17:38] Hmm ... is this big enough, can people see? [17:42] Okay, so basically I wrote this talk for Scotland JS, after the talk I broke half of the slides [17:52] and could not be bothered to redo all the slides because it was a total pain in the [17:56] ass in keynote to do it so I took much easier route (Laughing) of writing a tool that can [18:05] visualize the JavaScript Runtime at Runtime, and it's called loop. [18:11] So, let's just run this example and, which was kind of the example that we had on the [18:16] previous slide, I haven't shimmed XHR yet, it's doable I just haven't done it. [18:23] As you can see the code, we're going to log something, this is a shim around addEventListener, [18:30] setTimeout and we're going to do a console.log. ‑‑ [18:33] I'm going to run it and see what happens so ... add a DOM API, add a timeout, code is [18:40] going to continue to run, pushes the callback into the queue which runs, and we're done. [18:49] If I click on here then it's going to ... trigger the web API, queue the callback for the click [18:54] and run it. [18:56] if I cluck a hundred times we can see what happens. [18:59] I clicked, the click doesn't get processed immediately, itself gets pushed to the queue, [19:05] as the queue gets processed, eventually my click is going to get dealt with, right. [19:12] So I have a few more examples I'm going to run through here. [19:14] Here we go, okay, so, I'm just going to run through a few examples just to kind of talk [19:29] about a few things that you might have run in to and not thought about with Async APIs, [19:33] In this example we call setTimeout four times with the one second delay, and console.log [19:40] “hi”. [19:43] By the time the callbacks get queued... that fourth callback we asked for a one second [19:52] delay, and it's still waiting, the callback hasn't run, right . [19:56] this illustrates the ‑‑ like what time out is actually doing, it's not a guaranteed [20:02] time to execution, it's a minimum time to execution, just like setTimeout zero doesn't [20:06] run the code immediately it runs the code next‑ish, sometime, right? [20:16] So ... in this example I want to talk about callbacks, so, depending on who, speak to [20:24] and how they phrase things, callbacks can be one of two things, callbacks can be any [20:29] function that another function calls or callbacks can be more explicitly an asynchronous callback [20:34] as in one that will get pushed back on the callback queue in the future. [20:39] This bit of code illustrates the difference, right. [20:42] The forEach method on an array, it doesn't run, it takes a function, which you could [20:48] call a callback, but it's not running it asynchronously, it's running it within the current stack. [20:55] We could define an asynchronous forEach so it can take an array, a callback and for each [21:02] item in the array it's going to do a setTimeout zero with that callback, I guess this should [21:08] pass in the value, but any way, so, I'm going to run it and we can see what the difference [21:13] is, so for the first block of code that runs, it's going to sit and block the stack, right? [21:20] Until it's complete, whereas in the Async version, okay, it's slowed down, but we're [21:26] basically going to queue a bunch of callbacks and they're going to clear and then we can [21:31] actually run through and do a console.log. [21:34] In this example the console.log is fast, so the benefit of doing it asynchronously is [21:39] not obviously but let's say you're doing some slow processing on each element in the array. [21:48] I think I have that shown somewhere no, no, I don't. [21:55] Okay. [21:56] So let's say ‑‑ Ooops. [22:00] So I have a delay function which is just slow, it's just a slow thing. [22:06] So ... let's say processing Async and here processing Sync. [22:15] Okay, now, I'm going to turn on a thing I've literally hacked together this morning, which [22:23] is to simulate the repaint or the render in the browser, something I haven't touched on [22:27] is how all of this interacts with rendering ‑‑ I've kind of touched on it but not really [22:34] explained it. [22:36] So, basically the browser is kind of constrained by what you're doing javaScript, the browser [22:42] would like to repaint the screen every 16.6 milliseconds, 60 frame a second is ideal, [22:48] that's the fastest it will do repaints if it can. [22:52] But it's constrained by what you're doing in JavaScript for various reasons, so it can't [22:57] actually do a render if there is code on the stack, right. [23:02] Like the render kind of call is almost like a callback in itself. [23:05] It has to wait till the stack is clear. [23:07] The difference is that the render is given a higher priority than your callback, every [23:13] 16 milliseconds it's going to queue a rend, wait till the stack is clear before it can [23:21] actually do that render. [23:23] So this is ‑‑ this render queue is just simulating a render, every second it's can [23:27] I do a render? [23:28] Yes, can I do a render? [23:29] Yes. [23:30] Where, because our code isn't doing anything now. [23:33] If I run the code, you can see while we're doing this slow synchronous loop through the [23:39] array, our render is blocked, right, if our render is blocked you can't select text on [23:44] the screen, you can't click things and see the response, right, like the example I showed [23:50] earlier. [23:51] In this example, okay, it's blocked while we queue up the async time out, that relatively [23:57] quick but we're given ‑‑ we're kind of giving the render a chance between each element [24:04] because we've queued it up asynchronously to jump in there and do the render, does that [24:12] make sense? [24:13] >> Yeah >> Yeah, cool. [24:14] So, that's just kind of ‑‑ this is just like a simulation of how the rendering works, [24:19] but it just really shows you when people say don't block the event loop, this is exactly [24:23] what they're talking about. [24:24] They're saying don't put shitty slow code on the stack because when you do that the [24:27] browser can't do what it needs to do, create a nice fluid UI. [24:33] This is why when you're doing things like image processing or Animating too many things [24:38] gets sluggish if you're not careful about how you queue up that code. [24:44] So an example of that, we can see with the scroll handlers ‑‑ so scroll handle ‑‑ [24:53] like scroll events in the DOM trigger a lot, right, they trigger like ‑‑ I presume [24:58] they trigger on every frame like every 16 milliseconds, if I have code like this this [25:04] right. [25:05] On document.scroll, animate something, or do some work. [25:11] If I have this code, like as I scroll it's going to queue up like a ton of callbacks [25:16] right. [25:18] And then it has to go through and process all of those and each of the processing of [25:21] those is slow, then, okay, you're not blocking the stack, you're flooding the queue with [25:27] queued events. [25:29] So, this is like just helping visualize, I guess, what happens when you actually trigger [25:35] all these callbacks, there's way you can debounce that to basically say okay, we're going to [25:42] queue up all those events, but let's do the slow work every few seconds or until the user [25:48] stops scrolling for some amount of time I think that's basically it. [25:58] There's a whole other talk in how the hell this works. [26:03] Because basically in running the code, like this code runs at Runtime, right, and it's [26:09] slowed down by I run it through a Esprima a JavaScript parser, I insert a big while [26:17] loop, that takes half a second, it just slow motions the code. [26:23] Ship it to web worker and do a whole bunch of stuff to visualize what's happening while [26:28] doing it at run time that makes sense. [26:31] A whole other talk in that. [26:33] I'm super excited about it and will talk to anyone about it after because I think it's [26:38] kind of neat, so with that, thanks very much ( applause)