JavaScript是否为非阻塞AJAX生成线程?

11

一般认为JavaScript本质上是单线程的,但它可以异步运行。我想知道像这样的单线程模型如何处理非阻塞的AJAX请求?

假设在浏览器中发出了一个非阻塞的AJAX请求,但没有立即得到响应。如果事件循环一直检查响应,那么执行是否会被阻塞?当没有响应时,事件循环是否一直检查其状态并将任务“重新添加”到宏任务队列的末尾?

据我所知,Node.js会默认启用线程来处理访问磁盘、数据库、网络套接字等I/O操作。那么在浏览器中,JavaScript是否也会启动线程来处理AJAX请求?

类似的问题也可以被提出:

var img = new Image();
img.onerror=function(){alert('error: '+this.src);}
img.onload=function(){alert('image loaded: '+this.src);}
img.src='path/to/image.jpg';

上面的最后一行代码是否会导致生成一个额外的线程,因为该语句似乎是非阻塞的?


Ajax是阻塞的。要使用多线程JS,请使用Web Workers。https://nolanlawson.github.io/cascadia-2016/#/ - evolutionxbox
@evolutionxbox:不,通常它是非阻塞的。 - T.J. Crowder
@evolutionxbox 只有在将 .async = false 时才会这样。 - Jonas Wilms
“Javascript是单线程”的意思是,在许多线程中,特定的引擎只有一个解释JavaScript的线程。 - georg
1个回答

12
一般认为JavaScript本质上是单线程的,但它可以异步运行。 确实,JavaScript被规定为只有一个线程可以在任何给定时间内在执行环境中执行。 (执行环境是全局环境及其相关对象;例如浏览器中的窗口/选项卡。)您可以拥有多个活动线程(在不同的窗口中或通过Web工作程序),它们可以相互通信(通过postMessage),甚至可以共享一些内存(SharedArrayBuffer),但它们不能同时访问同一执行环境。保持执行环境有效地是单线程的,避免了大量的并发编程陷阱。
“我想知道像这样处理非阻塞AJAX请求的单线程模型会出现什么情况?” JavaScript仅允许在JavaScript环境中一次使用一个线程并不意味着主机(浏览器)是单线程的。 异步AJAX请求被交给浏览器的网络处理。

JavaScript基于作业队列工作(HTML5规范称其为任务队列,但JavaScript规范使用"jobs"——这只是一个名称)。JavaScript线程从队列中获取一个作业,运行该作业直至完成,然后获取下一个作业(如果有的话)。虽然实际上比这复杂一些,但这就是基本思想。当线程运行一个作业时,在同一领域中没有其他线程可以运行另一个作业。

因此,当ajax请求完成(成功、超时等)时,浏览器(可能在非JavaScript线程上)将一个作业放入JavaScript作业队列中以调用ajax回调函数。JavaScript线程获取该作业并调用回调函数。

值得注意的是,对于用户点击等其他事件,它也完全按照此方式响应。

假设在浏览器中发起了一个非阻塞的AJAX请求,但没有立即收到响应。如果事件循环继续检查响应,执行会被阻塞吗?

关键在于线程并不持续地检查响应。线程只是监视作业队列。浏览器的网络处理程序处理网络请求的完成。


¹ 这在 ES2015 中已经明确说明,但在此之前的常见环境(浏览器、Node.js)中也是如此。有一些 JavaScript 环境允许在一个域中使用多个线程(例如 Rhino,在 Java 虚拟机上运行 JavaScript),但它们被认为不够重要,不能阻止 ES2015 添加这个要求。这样做可以定义几个新功能的精确语义,如果保持对线程的沉默,则这将更加复杂甚至不可能。


好的...那么你是说有两个独立的线程,一个用于JavaScript,另一个用于浏览器? - Chong Lip Phang
@ChongLipPhang:我并不是特指两个线程。但浏览器将使用多个线程来运行JavaScript代码。我认为,虽然你可以制作只有一个总线程的浏览器,但它可能会是用户体验非常差的浏览器。它至少将拥有两个线程,可能更多。它将有一个用于JavaScript(实际上每个窗口/选项卡通常都有一个用于JavaScript),至少还有一个用于处理I/O完成等事务。 - T.J. Crowder
那么你的意思是浏览器网络处理程序运行在不同的线程上,当请求完成时,处理程序将回调放入队列中。对吗?那么所有浏览器 Web API,如 setTimeout、setInterval,它们也在不同的线程上运行? - Piyush
@Piyush - 它们可能在不同的线程上运行,或者它们可能被不同地实现。主JavaScript线程允许在作业之间执行非JavaScript工作,在浏览器上它会绘制UI。浏览器可能通过使线程检查(在作业之间)要触发的计时器来实现setTimeout等功能。(我认为当前的实现方式与此不同,但是...)对于任何更复杂的操作(如ajax),它们很可能使用单独的线程。 - T.J. Crowder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接