请指出我学习异步Javascript过程中的错误

Aja*_*ish 2 javascript asynchronous node.js

我是 Javascript 和异步编程的新手,有些事情让我感到困惑,请指出我学习中的错误。

  • 异步函数的回调被放入消息队列中并通过事件循环执行。
  • 异步执行是非阻塞的,通过事件循环完成。
  • 像这样的函数setTimeout是异步的。
  • 异步函数是阻塞的,只有它们的回调是非阻塞的。

以上如有错误,请指出。

VLA*_*LAZ 5

异步函数的回调被放入消息队列中并通过事件循环执行。

不。

通常有两种异步函数 - 一种在完成时需要某种回调来运行,另一种则生成 Promise 作为结果。

基于回调的函数

这是基于回调的函数的示例:

setTimeout(() => console.log("foo"), 0);

console.log("bar");

/* output: 
bar
foo
*/
Run Code Online (Sandbox Code Playgroud)

setTimeout将延迟稍后执行的回调。即使超时为零毫秒,它仍然会安排回调,直到当前执行完成之后,因此顺序是:

  1. setTimeout安排回调。
  2. console.log("bar")运行。
  3. 带有运行的回调console.log("foo")。

没有消息队列,有一个要执行的任务队列。作为一个简短的概述,事件循环从队列中获取一个任务并执行它直至完成,然后获取下一个任务并执行它直至完成,等等。一个简单的伪代码表示是:

while(true) {
    if (eventQueue.hasNext()) {
        task = eventQueue.takeNext();
        task();
    }
}
Run Code Online (Sandbox Code Playgroud)

有关事件循环的更多信息,请参阅此处。

setTimeout话虽如此,事件循环比许多其他基于回调的函数更具影响力。基于回调的函数的其他示例是用于文件系统访问的 jQuery$.ajax()或 Node.jsfs模块(其非承诺 API)。他们和其他人也接受稍后执行的回调,但边界因素不是事件循环,而是函数执行的任何底层操作。何时$.ajax() 调用回调取决于网络速度、延迟和响应请求的服务器。因此,虽然回调将由事件循环执行,但其他一切也是如此。所以,这没什么特别的。这里只有setTimeout一个更特殊的交互,因为根据可用的任务,计时可能不精确 - 如果您安排某些任务在 10 毫秒内运行,而某些任务需要 12 毫秒才能完成,则安排的回调将不会按时运行setTimeout。

基于 Promise 的函数

这是一个例子:

while(true) {
    if (eventQueue.hasNext()) {
        task = eventQueue.takeNext();
        task();
    }
}
Run Code Online (Sandbox Code Playgroud)

(为了便于说明,我现在省略了很多细节。)

从技术上讲,Promise 是针对处理异步操作而定制的回调的抽象。Promise 的方法.then()接受回调,并在Promise 解决后执行它,这也会在异步操作完成后发生。因此,我们可以按照正确的顺序一起执行:

async function fn(print) {
  await "magic for now";
  console.log(print);
}

fn("foo")
  .then(() => console.log("bar"));

/* output: 
foo
bar
*/
Run Code Online (Sandbox Code Playgroud)

在某种程度上,Promise 是一种回调,因为它们是用来替代 Promise 并以大致相同的方式执行的。您仍然可以传递回调以在成功或失败时执行。但它们不仅仅是回调。

无论如何,给予 Promise 的回调仍然会延迟:

async function fn(print) {
  await "magic for now";
  console.log(print);
}

fn("foo");
console.log("bar");

/* output: 
bar
foo
*/
Run Code Online (Sandbox Code Playgroud)

但不是通过与所setTimeout使用的相同的事件队列。有两个队列:

  • 宏任务队列 -setTimeout将东西放入其中,所有 UI 交互也添加到此处。
  • 微任务队列 - 承诺在这里安排他们的事情。

当事件循环运行时,微任务队列(即promise)具有最高优先级,然后是宏任务队列。这导致:

Promise.resolve()
  .then(() => console.log("foo"));
  
console.log("bar");


/* output: 
bar
foo
*/
Run Code Online (Sandbox Code Playgroud)

无论如何,我认为我不太愿意说基于 Promise 的函数通过回调来工作。有点是的,但是是在还原论的意义上。

异步执行是非阻塞的,通过事件循环完成。

不。

什么是“阻塞”?

首先,让我们明确一点 - 阻塞行为是指环境正忙于执行某些操作时。通常在该执行期间不能运行其他代码。因此,进一步的代码将被阻止运行。

我们以这段代码为例:

setTimeout(taskForLater, 5000);

while (somethingIsNotFinished()) {
    tryToFinish();
}
Run Code Online (Sandbox Code Playgroud)

这里taskForLater将被安排在5秒内运行。但是,while循环会阻塞执行。由于不会运行其他代码,taskForLater因此可能会在 10 秒内运行(如果这是循环完成所需的时间)。

运行异步函数并不总是意味着它与当前代码并行运行。在大多数情况下,环境一次执行一件事。JavaScript 默认情况下没有多线程,并行执行是一种选择行为,例如通过使用工人。

什么是“异步执行”?

这可能意味着几件事,但不清楚您引用的是哪一个:

  1. 运行异步函数的主体
  2. 等待底层异步操作完成

在这两种情况下,引用的陈述都是错误的,但原因不同。

异步函数的主体

异步函数是承诺的语法糖。它们使用相同的机制,但只是以不同的方式呈现代码。然而,异步函数是阻塞的。附带说明一下,Promise 执行器(给予 Promise 构造函数的回调)也是如此。在这两种情况下,该函数都会运行并阻塞,直到有原因导致其暂停。演示它的最简单方法是使用异步函数 - 使用await 将暂停函数的执行并安排其余部分稍后完成。

全身都堵住了:

setTimeout(() => console.log("foo")); //3 - macrotask queue

Promise.resolve()
  .then(() => console.log("bar")); //2 - microtask queue
  
console.log("baz"); //1 - current execution


/* output: 
baz
bar
foo
*/
Run Code Online (Sandbox Code Playgroud)

中间停顿一下:

setTimeout(taskForLater, 5000);

while (somethingIsNotFinished()) {
    tryToFinish();
}
Run Code Online (Sandbox Code Playgroud)

需要澄清的是,可以等待任何值,而不仅仅是承诺。等待非承诺仍然会暂停并恢复执行,但由于没有什么可等待的,这将导致它成为微任务队列上的首要任务之一。

无论如何,执行异步函数的主体可能会阻塞。取决于什么操作。

底层异步操作

当谈论“底层操作”时,大多数时候我们的意思是将控制权交给其他东西,例如浏览器或库。在这种情况下,当前的 JavaScript 环境不会完成该操作,我们称之为将执行操作并仅在操作完成时通知当前 JavaScript 环境的东西。例如,在浏览器中调用fetch将触发网络请求,但处理它的是浏览器,而不是我们的代码。所以它是非阻塞的,但不是在执行环境之外。

async function fn() {
  console.log("foo");
  
  console.log("bar");
}


fn();
console.log("baz");

/* output:
foo
bar
baz
*/
Run Code Online (Sandbox Code Playgroud)

话虽如此,我们甚至无法概括给定的异步操作将做什么。它实际上可能会阻塞执行,至少会阻塞一段时间,或者可能完全在当前 JavaScript 环境的单独进程中执行。

像这样的函数setTimeout是异步的。

是的。

虽然,它可能正在考虑是什么样的 setTimeout。是吗setInterval?它是基于回调的异步函数吗?问题在于定义开始变得循环“基于异步回调的函数(例如setTimeout异步函数)”。

并非每个接受回调的函数都是异步的。那些可能被认为类似于setTimeout。

异步函数是阻塞的,只有它们的回调是非阻塞的。

不。

如上所述,异步函数可能会阻塞。取决于他们具体做什么。例如$.ajax会使用浏览器发起网络请求,类似于fetch。如果$.ajax功能在准备请求时阻塞,但在发送请求后则不会阻塞。

该语句的第二个问题是,以非阻塞方式调用回调是不正确的——执行它们肯定会再次阻塞执行。回调是一个普通的 JavaScript 代码,当时间到来时将通过事件循环执行。当任务运行完成时,执行仍然被阻止。

如果您的意思是它们是非阻塞的,因为回调将被放入事件队列中,但这仍然不能保证。考虑以下说明性代码:

function myAsyncFunction(callback) {
    const asyncResult = someAsynchronousNonBlockingOperation();
    
    doStuff(result);
    callback(result);
    doMoreStuff(result);
}
Run Code Online (Sandbox Code Playgroud)

一旦someAsynchronousNonBlockingOperation()产生一个值,执行callback将不会被安排在以后执行,但将成为处理该结果的同步代码序列的一部分。因此,将稍后callback执行,但它本身不是一个任务,而是包含和 的整体任务的一部分。doStuffdoMoreStuff