JavaScript默认是同步(阻塞)还是异步(非阻塞)的?

22

我正在尝试理解JavaScript异步函数和回调函数。

我卡在回调函数的概念上,有些地方写道:它们用于代码的顺序执行(大多数情况下是在jQuery中,例如动画),而在Node.js的上下文中特别是用于并行执行异步操作以避免代码阻塞。

因此,这个话题中的一些专家能否给出例子,以便我可以搞清楚这个问题并理解如何使用回调函数。

或者这完全取决于您在代码中调用/放置回调函数的位置?

谢谢!

P.S:我害怕这个问题会被关闭,但我仍然希望能够得到具体的答案(也许还有一些例子)。

编辑:实际上,这是互联网上使我感到困惑的一个例子:

function do_a(){
  // simulate a time consuming function
  setTimeout( function(){
    console.log( '`do_a`: this takes longer than `do_b`' );
  }, 1000 );
}

function do_b(){
  console.log( '`do_b`: this is supposed to come out after `do_a` but it comes out before `do_a`' );
}

do_a();
do_b();

结果

`do_b`: this is supposed to come out after `do_a` but it comes out before `do_a`
`do_a`: this takes longer than `do_b`

据我的理解,当JS是顺序执行时,do_b 应该始终在 do_a 之后执行。


8
JavaScript就是JavaScript,它取决于上下文、用途、引擎等。 - Dave Newton
你能提供一些示例代码,让我们确定它是阻塞还是非阻塞的吗? - Matt
JavaScript 通常是同步的,但 setTimeout 根据定义是异步的。这里有一个很好的入门介绍:https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout - Colin DeClue
5个回答

27

JavaScript的核心是基本上同步的,也就是说函数在完成任务之前会完全执行。在AJAX出现之前,只有setTimeout和setInterval提供了异步行为。

然而,很容易忘记事件处理程序实际上是异步代码。附加处理程序不会调用处理程序代码,并且该代码直到未来的某个时间才执行。

然后,AJAX出现了,它通过调用服务器来实现。这些调用可以配置为同步的,但开发人员通常更喜欢异步调用并使用回调方法来实现它们。

然后,我们看到JS库和工具包的大量使用。这些努力使不同浏览器对事物的实施方式变得统一,并建立在回调方法对异步代码的处理方式上。您还将看到更多使用同步回调来处理数组迭代或CSS查询结果等事项。

现在,我们在混合中看到了Deferreds和Promises。这些是表示长时间运行操作的对象,并提供处理该值到达时的API。

NodeJS倾向于许多事情采用异步方法;这是真实的。然而,这更多是他们自己的设计决策,而不是JS固有的任何异步特性。


14

Javascript始终是一种同步(阻塞)单线程语言,但我们可以通过编程使Javascript表现为异步。

同步代码:

console.log('a');
console.log('b');

异步代码:

console.log('a');
setTimeout(function() {
    console.log('b');
}, 1000);
setTimeout(function() {
    console.log('c');
}, 1000);
setTimeout(function() {
    console.log('d');
}, 1000);
console.log('e');

这将输出:a e b c d


在这个上下文中,“blocking”是什么意思? - Sandeepan Nath
1
@SandeepanNath 你可能已经明白了,但对于未来的访问者来说:阻塞是指串行处理。一次只做一件事。另一方面,非阻塞代码(异步)可以并行运行(或多线程)。优点是它可以更快。 - Connor

3
在 Node 中,长时间运行的进程使用 process.nextTick() 来排队函数/回调。这通常在 Node 的 API 中完成,除非你在编写阻塞或长时间运行的代码(在 API 之外),否则它不会对你产生太大影响。下面的链接应该比我更好地解释了这一点。

howtonode process.nextTick()

jQuery AJAX 也采用回调等方式,因为它的编码方式是不等待服务器响应就继续执行下一块代码。它只记住当服务器响应时要运行的函数。这基于浏览器公开的 XMLHTTPRequest 对象。XHR 对象将记住在响应返回时要回调的函数。
javascript 的 setTimeout(fn, 0) 将在调用堆栈为空(下一个可用空闲 tick)时运行函数,这可以用于创建类似异步的特性。setTimeout(fn, 0) question on stackoverflow 总结一下,JavaScript 的异步能力与它们编程的环境有关,而不仅仅是 JavaScript 本身。除非你使用某些 API/脚本,否则仅使用许多函数调用和回调并不会获得任何神奇的效果。 Jquery Deferred Object 是另一个关于 jQuery 异步能力的好链接。通过谷歌搜索,你还可以找到有关 jQuery Deferred 的信息,以获得更多的见解。

1
在JavaScript中,“异步”一词通常指的是在调用堆栈为空且引擎从其作业队列中选择一个“作业”进行执行时执行的代码。
一旦代码正在执行,它表示同步执行序列,直到再次为空为止。这个执行序列不会被事件打断以执行其他JavaScript代码(当我们放弃Web Workers时)。换句话说,单个JavaScript环境没有抢占式并发。
在同步执行期间,事件可能会被注册为某些作业队列中的作业,但引擎在正确执行调用堆栈上的内容之前不会处理这些作业。只有在调用堆栈为空时,引擎才会注意到作业队列,根据优先级选择一个作业并执行它(这就是所谓的异步)。
回调可以是同步或异步的 - 这实际上取决于它们如何被调用。
例如,在这里,回调是同步执行的:
new Promise(function (resolve) { /*  .... */ });

这里回调函数是异步执行的:

setTimeout(function () { /* ... */ });

这真的取决于将回调作为参数传递的函数; 它如何处理最终调用该回调。

实现异步执行代码的方式

核心ECMAScript语言没有提供很多的实现方式。众所周知,这些方法是通过其他API(例如Web API)提供的,它们不是核心语言的一部分(setTimeout, setInterval, requestAnimationFrame, fetch, queueMicrotask, addEventListener等)。

Core ECMAScript提供了Promise.prototype.then和(根据此)await。传递给then的回调保证异步执行。await将确保同一函数中的下一条语句将异步执行:await使当前函数返回,并且将由作业恢复此函数的执行上下文并继续执行。

它还提供了监听垃圾收集器即将回收对象的时间的功能,使用FinalizationRegistry

Web Workers

Web Workers将在单独的执行环境中执行,具有自己的调用堆栈。这里可以实现抢占式并发。当JavaScript世界中使用“异步”一词时,通常不是指这种并行性,尽管通过异步回调函数与Web Worker进行通信。


-1

Javascript默认是“同步”的,而是Web API处理“异步”行为。

至于setTimeout的例子,

console.log(...)在全局范围内立即工作,而那些包含在setTimeout内部函数中的内容则等待在回调队列中,直到准备好后才被推回调用堆栈。因此它们需要时间。此外,指定的时间不是精确的,而是代码可以随时运行的最短时间。

谢谢!


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