在 JavaScript 中,异步代码是如何真正工作的?

Gar*_*ary 0 javascript node.js

过去两周我一直在学习以下内容:setImmediate、process.nextTick、setTimeout、promises、callbacks、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 ...');
Run Code Online (Sandbox Code Playgroud)

输出是:

18:09:48 starting ...
18:09:52 end of program ...
18:09:52 #1 completed ...
18:09:52 #2 completed ...
Run Code Online (Sandbox Code Playgroud)

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

为什么?

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

Kla*_*con 5

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

您实际上有两个选择:

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

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

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