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

1 javascript web-worker promise

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

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

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

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

这是从 Promise 获取“非阻塞”行为的典型示例,但如果您在 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>
Run Code Online (Sandbox Code Playgroud)

相反,如果我们用循环创建时间延迟,很明显 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>
Run Code Online (Sandbox Code Playgroud)

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

Kro*_*tan 5

Promise 不是线程,JS 很大程度上是单线程的,并且在事件队列中工作。事实上,promise 只是组织回调。

如果您想运行 CPU 密集型代码,那么您需要使用Web Workers,并使用它们的接口与它们进行通信postMessage(如果您愿意,可以将其包装成 Promise 返回格式)。它们确实作为在单独线程上运行的脚本运行,但请注意它们的通信是如何受到限制的,它不像经典的多线程那样自由支配内存访问。

Web Workers 将无权访问您的窗口,因此不会对其中的 DOM 进行修改。但是,如果您有复杂的模拟,您可以巧妙地将数据模型与显示分开,只需将数据模型传输到工作人员或从工作人员传出数据模型,然后使用普通脚本将其转换为 UI。

要了解有关 JavaScript 执行模型的更多信息,我建议您参阅MDN 上的介绍,如果您想深入了解其实现方式,请参阅 Jake Archibald 的演示文稿

  • @kroitan 不完全是,有 SharedArrayBuffers 可以在不同的执行环境之间共享。 (2认同)