"Single-threaded" is true of one agent, and misleading for a whole program.
- The language. The spec defines an agent: one execution context stack, one set of jobs, and an executing thread. Jobs run to completion, one at a time. Two pieces of JavaScript in the same agent never run at the same time, so a variable cannot change between two synchronous statements.
- The host. Waiting is not done by your thread. Browsers and Node.js do network, timer and file work outside the JavaScript thread (in Node.js, partly in a worker pool of threads) and queue a callback when it finishes. That is why
await fetch()does not freeze the page. - More agents. Web Workers in browsers and
worker_threadsin Node.js run JavaScript on other threads. Each worker is a separate agent with its own global object, heap and event loop. Objects are not shared:postMessagecopies values (structured clone), or transfers ownership of some objects such as anArrayBuffer. - Shared memory. A
SharedArrayBuffercan be passed to several agents in the same agent cluster. UseAtomicsfor race-free updates and for waiting.Atomics.waitblocks the thread, so it is not allowed on a browser's main thread.
A good answer: JavaScript has no shared-memory threads by default and no preemption within an agent, but a program can use several threads through the host and workers.