如何在 Javascript 中执行非阻塞调用? (承诺不会那样做。)

How to perform a non-blocking call in Javascript? (Promises don't do that.)

在 Javascript 运行 现代浏览器中,我想对函数进行非阻塞调用。

有人向我指出 Promises 和构建在它们之上的异步函数,但我发现 Promises(以及异步函数)确实会阻塞。请参阅下面的代码来演示这一点。

唯一不阻塞的函数似乎是内置的。例如设置超时。这似乎就是为什么我发现的所有 'nonblocking' Promise 示例都使用 setTimeout。单个执行线程遍历超时代码并调用非阻塞 setTimeout 以继续步进。 Promise 本身只是组织回调,这很好,但它们本身不会导致调用成为非阻塞的。

webworker 解决方案似乎没有引用,因此无法修改调用者的数据。序列化、调用 web worker,然后反序列化以取回结果将是相当低效且复杂的。

这是从 Promise 获取 'non-blocking' 行为的典型示例,但如果您在 Web 控制台中单步执行它,您将看到唯一不会阻塞的是 setTimeout。

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <script>
    'use strict';

    function wait_setTimeout(call_when_finished){
      console.log("entering wait_setTimeout");
      setTimeout(call_when_finished, 2000); // 3 second delay
    }
    function wait_setTimeout_resolve(){
      console.log("wait_setTimeout_resolved");
    }

    console.log("before wait_setTimeout promise");
    let p0 = new Promise((wait_setTimeout_resolve) => {
      this.wait_setTimeout(wait_setTimeout_resolve);
    });
    console.log("after new Promise(wait_setTimeout_resolve)");
    p0.then(() => console.log("then wait_setTimeout_promise target"));
    console.log("after wait_setTimeout_promise.then");
    /*
     before wait_setTimeout promise
     entering wait_setTimeout
     after new Promise(wait_setTimeout_resolve)
     after wait_setTimeout_promise.then

     <delay occurs here, as it should, but because setTimeout didn't block, not the Promise>

     then wait_setTimeout_promise target
    */
  </script>
</body>

如果我们用循环创建时间延迟,则很明显 Promise 正在阻塞:

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <script>
    'use strict';

    let limit = 100000; // ~3 second delay, make this bigger if you don't see a delay
    function wait_loop(call_when_finished){
      console.log("entering wait_loop");
      let i = 0;
      let j;
      while(i < limit){
        i++;
        j = 0;
        while(j <  limit){
          j++;
        }}
      call_when_finished();
    }
    function wait_loop_resolve(){
      console.log("wait_loop_resolved");
    }

    console.log("before wait_loop promise");
    let p1 = new Promise((wait_loop_resolve) => {
      this.wait_loop(wait_loop_resolve);
    });
    console.log("after new Promise(wait_loop_resolve)");
    p1.then(() => console.log("then wait_loop_promise target"));
    console.log("after wait_loop_promise.then");
    /*
     before wait_loop promise
     entering wait_loop

     <delay occurs here..  i.e. the new Promise blocked>

     after new Promise(wait_loop_resolve)
     after wait_loop_promise.then
     then wait_loop_promise target
    */
  </script>
</body>

当然循环只是一个占位符。实际代码正在计算所需的东西。

PromiseConstructor 调用一个名为 executor 的参数,它必须是一个函数并且与两个参数 resolvereject.

同步执行

因此,在您的代码中,执行器 (wait_loop_resolve) => { this.wait_loop(wait_loop_resolve); } 会立即执行,这意味着您的函数 wait_loop 也会被执行。您可以使用 setTimeout(wait_loop, 0)Promise.resolve().then(wait_loop) 异步执行它。如果需要传递参数,可以写成如下格式:

setTimeout(() => wait_loop(wait_loop_resolve), 0)

// or with promise
Promise.resolve(wait_loop_resolve).then(wait_loop)

Promise 不是线程,JS 非常single-threaded,并且在事件队列中工作。 Indeed 承诺只是组织回调。

如果你想 运行 CPU-intensive 代码,那么你需要使用 Web Workers,并使用他们的界面与他们交流,postMessage(你如果您愿意,可以包装成 Promise-returning 格式)。它们确实作为脚本 运行 在单独的线程上运行,但请注意它们的通信是如何受到限制的,它不像经典多线程那样 free-reign 内存访问。

Web Workers 将无法访问您的 window,因此不会对它们进行 DOM 修改。但是,如果您有复杂的模拟,您可以巧妙地将数据模型与显示分开,并且只需将数据模型传输到 worker 或从 worker 传输数据模型,使用普通脚本将其转换为 UI.

要了解有关 JavaScript 执行模型的更多信息,请参阅 introduction on MDN, and if you want a very deep view on how that is implemented, this presentation by Jake Archibald

我想你搞错了。

  1. 你要的是multi-thread,不是async。
  2. 异步与 multi-thread 无关,异步可以 运行 在单线程上。

在您的情况下,当您可以 then 时,您的线程已 释放 ,因此它将立即调用并等待结果,但如果您需要这样做其他诸如渲染、解码等...向用户显示加载页面!这就是异步的目的。

如果您需要 pre-do 东西。例如:pre-decoding 某种数据,pre-compute 图片等...是的,我的意思是你必须 pre 做!或者使用网络工作者。问题是您必须知道数据并在将其显示给用户之前预先计算它。

关于异步的更多信息是