【问题标题】:How does async code really work under the hood in JavaScript? [duplicate]异步代码在 JavaScript 的底层是如何工作的? [复制]
【发布时间】:2020-11-30 13:28:05
【问题描述】:

过去两周我学习了以下内容: setImmediate、process.nextTick、setTimeout、promise、回调、libuv、事件循环、作业/微任务队列、事件循环队列、调用堆栈等。

我真的掉进了一个无法逃脱的兔子洞,虽然我发现自己更了解情况,但我仍然难以掌握 JavaScript 中的异步代码。

我想了解以下基本场景并了解如何异步实现它:

// does nothing; here to simulate functionality below
var data = new Array(10000000);

const displayTime = desc => {
  var time = new Date();
  console.log(
    ("0" + time.getHours()).slice(-2) + ":" +
    ("0" + time.getMinutes()).slice(-2) + ":" +
    ("0" + time.getSeconds()).slice(-2) + " " + desc
  );
}

displayTime('starting ...');

// --- async/await (a promise):

const processData = async(data) => {
  let dataLen = data.length;
  let processedData = [];
  //console.time('#1');
  for (let ctr = 0; ctr < dataLen; ctr++) {
    // something happens here; simulating a long task using a for-loop;
    // for purposes of this question, let's just assume it's necessary to do this
    processedData.push(ctr / 33 * 383739722);
  }
  //console.timeEnd('#1');
  return processedData;
}

(async() => {
  result = await processData(data);
  // console.log(result);
  displayTime('#1 completed ...');
})();

// --- promise:
const processData2 = data => {
  return new Promise((resolve, reject) => {
    let dataLen = data.length;
    let processedData = [];
    //console.time('#2');
    for (let ctr = 0; ctr < dataLen; ctr++) {
      processedData.push(ctr / 33 * 383739722);
    }
    //console.timeEnd('#2');
    resolve(processedData)
  });
}

processData2(data).then(data => {
  // console.log(data);
  displayTime('#2 completed ...');
});

displayTime('end of program ...');

输出是:

18:09:48 starting ...
18:09:52 end of program ...
18:09:52 #1 completed ...
18:09:52 #2 completed ...

从输出中可以看出,“程序结束...”直到两个长时间运行的进程完成后才回显到屏幕上(见时间)。

为什么?

我怎样才能在后台运行这两个任务(它们都使用承诺),以便它们不会立即阻塞事件循环和我的“程序结束...”字符串回显?

【问题讨论】:

  • 声明函数async 不会使其异步运行。它只是返回 promise 的语法糖,这使得使用 if 它执行异步代码变得更容易,并允许它在内部使用 await 来调用其他异步函数。
  • 打电话给setTimeout
  • setTimeout 异步执行其回调函数。
  • 当我运行您的代码 sn-p 时,我只会收到 starting 消息和错误屏幕。你抄对了吗?
  • @Barmar 似乎 Gary 有一些同步处理,他正试图异步执行,即以非阻塞方式。在这种情况下,唯一的选择是工作线程。

标签: javascript node.js


【解决方案1】:

JavaScript 总是在单线程中运行。如果您有繁重的同步处理负载,仅仅在异步上下文中删除同步负载是不够的:执行可能会延迟,但是当函数执行时间到时,其中的所有代码仍会同步执行。

您实际上有两个选择:

  1. 使用 node.js 的 cluster api 生成工作线程并让工作线程执行同步处理
  2. 通过使用 setImmediate 之类的方法在某些点手动延迟您的同步处理,并将处理分成可以一次处理一个的离散块。

对于您的研究,您可能有兴趣阅读 node.js 本身 uses worker threads 以了解文件系统操作。我相信网络操作是通过轮询来处理的。在这两种情况下,所有处理负载都发生在 node.js 主进程之外(在另一个线程或另一台计算机上)。如果您的目标是让同步处理负载成为非阻塞式,您也应该这样做。

我还在 node.js 文档中找到了这个出色的指南,它基本上涵盖了我上面描述的内容,但使用了更好的术语和更多细节:https://nodejs.org/en/docs/guides/dont-block-the-event-loop/#complex-calculations-without-blocking-the-event-loop

【讨论】:

  • 所以只是为了总结我对这个主题的理解:我应该只使用确实是异步的代码的承诺。除非我的目的是推迟?
  • 是和不是。您可以(并且经常这样做)链接承诺并同步执行某些部分。
  • async function x() { return 1; } 等同于写function x() { return Promise.resolve(1); }
  • @Gary 或多或少。当使用使用它们的 API(本机或库)时,Promise 非常棒,或者您有时可以让它们包装旧的回调样式 API。大多数情况下,这只对涉及网络请求和文件系统操作的事情有用。如果您需要处理繁重的处理负载,线程是不二之选。
猜你喜欢
  • 2014-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多