Does JavaScript spawn threads for non-blocking AJAX?

The general perception is that JavaScript is intrinsically single-threaded but it can run asynchronously.

It's true that JavaScript is specified¹ such that only a single thread can be executing within a realm at any given time. (A realm is the global environment and its associated objects; e.g. a window/tab [on browsers].) You can have multiple active threads (in different windows, or via web workers), and they can talk to each other (via postMessage) or even share some memory (SharedArrayBuffer), but they can't access the same realm at the same time. Keeping realms effectively single-threaded avoids a huge range of concurrent programming pitfalls.

I wonder how a single-threaded model like this handles AJAX requests that are non-blocking?

JavaScript allowing only one thread within the JavaScript environment at a time doesn't mean that the host (the browser) is single-threaded. An asynchronous ajax request is handed off to the browser's network handling.

JavaScript works on the basis of a job queue (the HTML5 speec calls it a task queue, but the JavaScript spec speaks of "jobs" — it's just a name). The JavaScript thread picks up a job from the queue, runs that job to completion, and then picks up the next job, if any. (It's a bit more complicated than that, but that's the basic idea.) While the thread is running a job, no other thread can run another job in the same realm.

So when an ajax request completes (success, timeout, whatever), the browser (on a non-JavaScript thread, probably) puts a job in the JavaScript job queue to call the ajax callback. The JavaScript thread picks up that job and calls the callback.

It's worth noting that that is also exactly what it does in response to other things that happen, such as the user clicking something.

Lets say a non-blocking AJAX request is fired in a browser, but doesn't get a response immediately. If the event loop keeps checking for the response, doesn't the execution get blocked?

The key is that the thread doesn't continually check back for a response. The thread just watches the job queue. The browser's network handler handles completion of network requests.


¹ This was made explicit in ES2015, but it was the case for common environments (browsers, Node.js) for years prior to that. There were JavaScript environments that allowed multiple threads in a realm (like Rhino, running JavaScript on the Java VM), but they weren't considered important enough to prevent ES2015 adding this requirement, and doing so allowed defining precise semantics around several new features that would have been much more complicated to specify, if it was even possible, while remaining silent on the subject of threading.