【问题标题】:Is async await truly non-blocking in the browser?浏览器中的异步等待真的是非阻塞的吗?
【发布时间】:2017-08-04 01:32:29
【问题描述】:

我一直在使用 TypeScript 和本机 Promises 在 SPA 中使用该功能,我注意到即使我将长时间运行的函数重构为返回 promise 的异步函数,UI 仍然没有响应。

所以我的问题是:

  • 新的 async/await 功能究竟如何帮助避免阻塞浏览器中的 UI?在使用 async/await 来真正获得响应式 UI 时,是否需要采取任何特殊的额外步骤?

  • 谁能创建一个小提琴来演示 async/await 如何帮助使 UI 响应?

  • async/await 与之前的异步功能(如 setTimeout 和 XmlHttpRequest)有何关系?

【问题讨论】:

  • 被阻塞的代码仍然会被阻塞。如果没有,您可能会有数据竞争。异步函数的想法是您可以暂停以等待稍后执行异步代码。因此,您在等待异步完成时暂停,例如 setTimeout、XHR 响应或单击事件:jsfiddle.net/wgqyayhr (演示需要支持的浏览器)
  • async/await 不是 ES7 (ES2016) 的一部分。它将成为今年发布的 ES2017 的一部分。

标签: javascript user-interface asynchronous async-await ecmascript-2017


【解决方案1】:

await p 会在 Promise p 解决时安排其余函数的执行。就是这样。

async 允许您使用await。这就是(几乎)它所做的一切(它还将你的结果包装在一个承诺中)。

它们一起使非阻塞代码读起来像更简单的阻塞代码。他们不会解锁代码。

对于响应式 UI,将 CPU 密集型工作卸载到 worker 线程,并将消息传递给它:

async function brutePrime(n) {
  function work({data}) {
    while (true) {
      let d = 2;
      for (; d < data; d++) {
        if (data % d == 0) break;
      }
      if (d == data) return self.postMessage(data);
      data++;
    }
  }

  let b = new Blob(["onmessage =" + work.toString()], {type: "text/javascript"});
  let worker = new Worker(URL.createObjectURL(b));
  worker.postMessage(n); 
  return await new Promise(resolve => worker.onmessage = e => resolve(e.data));
}

(async () => {
  let n = 700000000;
  for (let i = 0; i < 10; i++) {
    console.log(n = await brutePrime(n + 1));
  }
})().catch(e => console.log(e));

【讨论】:

  • 没有外部文件的工人的积分。这是一个很酷的技巧。
  • 异步函数也返回一个承诺
【解决方案2】:

async 是一种更优雅的异步代码结构方式。它不允许任何新功能;它只是比回调或承诺更好的语法。

所以,async 不能用于“使某些东西异步”。如果您的代码必须进行大量基于 CPU 的处理,async 不会神奇地使 UI 响应。您需要做的是使用web workers 之类的东西,它将受 CPU 限制的工作推送到后台线程以使 UI 响应的合适工具。

【讨论】:

  • 我猜你可以像这样使用它来停止循环阻塞。 for (let i = 0; i setTimeout(resolve, ms)); }
  • @Martian2049:是的; async 建立在 Promises 之上,它是非阻塞的。
  • “它不允许任何新功能” 它确实允许。 “所以,async 不能用于“使某些东西异步” 但是await 可以。
  • @abetteroliver:也许我们正在研究不同的语义。当我说“[async] 不允许任何新功能”时,我的意思是你可以用 async 做的所有事情都可以在没有 async 使用回调/承诺的情况下完成 - 这是可以简单证明的,因为直到最近 async 是没有运行时支持的编译时代码转换。
  • async 已经有一段时间了,您所指的转换使用生成器。生成器函数和异步函数都保留堆栈,并且本质上允许函数暂停。您无法使用 Promise 实现这一目标。当然,您是对的,await 不会使 API 异步,但由于(本机)承诺的异步性质,其他代码可能(!)有机会在函数继续之前执行。
【解决方案3】:

JavaScript 是单线程的,并且与 UI 在同一个线程中运行。所以所有的 JavaScript 代码都会阻塞 UI。正如其他人提到的,网络工作者可用于在其他线程中运行代码,但它们有局限性。

异步函数和普通函数的区别在于它们返回一个promise。使用回调,您可以延迟代码的执行,该代码处理函数调用的结果,从而允许 UI 完成一些工作。下面三个例子的效果是一样的:

async function foo() {
  console.log("hi");
  return 1; 
}
foo().then(result => console.log(result))
console.log("lo");

function foo() {
  console.log("hi");
  return 1; 
}
Promise.resolve(foo()).then(result => console.log(result))
console.log("lo");

function foo() {
  console.log("hi");
  return 1; 
}
const result = foo();
setTimeout(() => console.log(result));
console.log("lo");

在所有三种情况下,控制台都会记录 hi, lo, 1。在打印 1 之前,UI 可以处理用户输入或绘制更新。前两种情况最后打印的原因 1 是 promise 的回调没有立即执行。

await 允许您在没有回调的情况下执行此操作:

async function foo() {
  console.log("hi");
  return 1; 
}

async function bar() {
  const result = await foo();
  console.log(result);
}

bar();
console.log("lo"); 

这也会打印 hi, lo, 1。很像一个 promise 的回调,await 之后的代码永远不会立即执行。

【讨论】:

  • 如果等待 bar() 怎么办?
  • @Rodrigo 你只能在异步函数中等待。
  • 为什么hilo 之前打印?
  • @zzzzzzz barconsole.log("lo") 之前被调用,它调用foo,后者调用console.log("hi")
  • @zzzzzzz 异步函数返回一个承诺。 await 等待承诺被解决或拒绝,并且总是异步发生。严格意义上没有等待时间,只是任何同步代码都会在此之前执行。
【解决方案4】:

developer.mozilla.org 的描述中可以清楚地看出它是非阻塞的:

await 关键字会导致 JavaScript 运行时暂停您的代码 这一行,允许其他代码同时执行(注意:我的粗体),直到 异步函数调用已返回其结果。一旦完成, 您的代码从下一行开始继续执行。

【讨论】:

  • "await 关键字会导致 JavaScript 运行时暂停您的代码在这一行,在此期间不允许进一步的代码执行,直到异步函数调用返回其结果 - 如果后续代码依赖于结果!”
  • 截至 2022 年 1 月 27 日,您分享的链接没有这样说。好像说反了。
【解决方案5】:

我在这里聚会迟到了。我想验证同步和异步用例。

需要明确的是,async/await 不会创建同步代码。它只添加了syntactic sugar,使代码看起来是同步的。在包装器下,Promise 逻辑继续实现non-preemptive multitasking

在示例 gist 中,您可以使用命令行参数运行示例,该参数选择阻塞或非阻塞 CLI 输入。 asyncExample.js

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-04
    • 2016-03-13
    • 1970-01-01
    • 2011-04-03
    • 2012-10-02
    • 2015-11-06
    • 1970-01-01
    • 2023-03-22
    相关资源
    最近更新 更多