69

I have been playing around with the feature in an SPA using TypeScript and native Promises, and I notice that even if I refactor a long-running function into an async function returning a promise, the UI is still unresponsive.

So my questions are:

  • How exactly does the new async/await feature help avoid blocking the UI in the browser? Are there any special extra steps one needs to take when using async/await to actually get a responsive UI?

  • Can someone create a fiddle to demonstrate the how async/await helps to make the UI responsive?

  • How does async/await relate to prior async features such as setTimeout and XmlHttpRequest?

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
prmph
  • 7,616
  • 11
  • 37
  • 46
  • 5
    Code that blocks will still block. If it didn't, you could have data races. The idea of async functions is that you can halt in place to wait for the later execution of asynchronous code. So you halt while waiting for something async to complete, like a `setTimeout`, an XHR response, or event a click event: https://jsfiddle.net/wgqyayhr/ *(Demo needs a browser with support)* –  Mar 13 '17 at 21:54
  • `async/await` is **not** part of ES7 (ES2016). It will be part of this year's release, ES2017. – Felix Kling Mar 16 '17 at 06:18

5 Answers5

109

await p schedules execution of the rest of your function when promise p resolves. That's all.

async lets you use await. That's (almost) all it does (It also wraps your result in a promise).

Together they make non-blocking code read like simpler blocking code. They don't unblock code.

For a responsive UI, offload CPU-intensive work to a worker thread, and pass messages to it:

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));
jib
  • 40,579
  • 17
  • 100
  • 158
  • 31
    Points for the worker without an external file. That's one cool trick. – solarc Jul 19 '17 at 22:44
  • 1
    async functions also return a promise – c0de Jun 25 '20 at 16:37
  • 1
    "await p schedules execution of the rest of your function when promise p resolves. That's all." --> Best and most concise explanation of async/await I have read on the entire internet. – rsp1984 Mar 20 '22 at 17:10
30

async is a more elegant way to structure asynchronous code. It doesn't allow any new capabilities; it's just a better syntax than callbacks or promises.

So, async can't be used to "make something asynchronous". If you have code that has to do lots of CPU-based processing, async isn't going to magically make the UI responsive. What you'd need to do is use something like web workers, which are the proper tool to push CPU-bound work to a background thread in order to make the UI responsive.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    I guess you could use it like this, to stop a loop blocking. for (let i = 0; i < 100000; i++) { await delay(10) } async function delay(ms) { return new Promise((resolve, reject)) => setTimeout(resolve, ms)); } – Will Voelcker Dec 18 '17 at 13:03
  • 2
    @Martian2049: Yes; `async` is built on Promises, which are nonblocking. – Stephen Cleary May 24 '18 at 15:03
  • _"It doesn't allow any new capabilities"_ It actually does. _"So, async can't be used to "make something asynchronous"_ But `await` can. – a better oliver Nov 01 '18 at 11:16
  • 2
    @abetteroliver: Perhaps we are looking at different semantics. When I say "[async] doesn't allow any new capabilities", I mean that everything you can do with `async` can be done without `async` using callbacks/promises - which is trivially provable because until recently `async` was a compile-time code transformation with no runtime support. – Stephen Cleary Nov 02 '18 at 13:01
  • @abetteroliver: Regarding "But `await` can [make things asynchronous]", I think of `await` as *consuming* asynchrony; it can't be used to make a synchronous API act asynchronously ("make things asynchronous"). – Stephen Cleary Nov 02 '18 at 13:04
  • 1
    `async` has been available for quite some time now and the transformation you are referring to uses generators. Both generator functions and async functions preserve the stack and essentially allow functions to be paused. You cannot achieve that using promises. You are right, of course, that `await` doesn't make an API asynchronous, but due to the asynchronous nature of (native) promises other code may(!) get the chance to be executed before the function continues. – a better oliver Dec 10 '18 at 20:15
24

JavaScript is single-threaded and runs in the same thread as the UI. So all JavaScript code will block the UI. As mentioned by others web workers can be used to run code in other threads, but they have limitations.

The difference between async functions and regular ones is that they return a promise. Using a callback you can then defer the execution of code, that handles the result of a function invocation and thereby allowing the UI to do some work. The following three examples have the same effect:

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");

In all three cases the console logs hi, lo, 1. Before 1 is printed the UI can handle user input or draw updates. The reason 1 that is printed last in the first two cases is that callbacks for promises are not executed immediately.

await allows you to do that without callbacks:

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

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

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

That will also print hi, lo, 1. Much like a callback for a promise, the code after await is never executed immediately.

a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • What if you await bar() ? – Rodrigo Oct 25 '18 at 16:43
  • 2
    @Rodrigo You can only await inside async functions. – a better oliver Nov 01 '18 at 11:08
  • Why does `hi` get printed before `lo`? – zzzzzzz Mar 29 '19 at 15:18
  • 1
    @zzzzzzz `bar` is called before `console.log("lo")` and it calls `foo` which calls `console.log("hi")`. – a better oliver Mar 30 '19 at 16:14
  • @abetteroliver my bad. I meant to ask, why does `1` get printed after `lo`? Does `await` automatically introduce a little bit of wait time? IOW, why does this behavioral work despite having no sleep timeouts – zzzzzzz Apr 03 '19 at 17:09
  • 2
    @zzzzzzz Async functions return a promise. `await` waits for the promise to be resolved or rejected and that always happens asynchronously. There is no waiting time in a strict sense, it's just that any synchronous code gets executed before that. – a better oliver Apr 08 '19 at 17:18
  • I was confused as well, then I copy & paste the code to https://babeljs.io/repl, and you can see it actually became 2 generator functions. I guess thats why – Alan Dong Mar 02 '20 at 22:37
  • I've been using async await for years and I am confused as hell also. Let me try to give my own explanation based on how I understand it. Async await only blocks execution within the context of the async function, however, it can still return the control to the javascript engine to execute code outside of the function while awaiting Promises inside the function. Is that correct? – The.Wolfgang.Grimmer May 27 '23 at 11:35
  • @The.Wolfgang.Grimmer Imagine you are the javascript engine, and heating something in the oven is a function. Without async / await, you have to sit in front of the oven until it's finished. You are blocked. With async / await, you can do something else in the meantime. You are not blocked. The oven is not blocked. Nothing is blocked. Now replace heating with sending a request. Until the response comes in, the engine has nothing to do. By using `await`, the engine knows it can do something else in the meantime. – a better oliver Aug 16 '23 at 12:49
2

It's clear from the description on developer.mozilla.org that it's non-blocking:

The await keyword causes the JavaScript runtime to pause your code on this line, allowing other code to execute in the meantime (Note: my bold), until the async function call has returned its result. Once that's complete, your code continues to execute starting on the next line.

Per Quested Aronsson
  • 11,380
  • 8
  • 54
  • 76
  • 1
    "The await keyword causes the JavaScript runtime to pause your code on this line, not allowing further code to execute in the meantime until the async function call has returned its result — very useful if subsequent code relies on that result!" – Rachanee Saengkrajai Jan 14 '21 at 05:54
  • 1
    As of January 27, 2022, the link you shared does not say this. It seems to say the opposite. – ddejohn Jan 28 '22 at 00:10
0

I'm late to the party here. I wanted to verify both the synchronous and asynchronous use cases.

To be clear, async/await does not create synchronous code. It only adds syntactic sugar that makes the code look synchronous. Under the wrappers, the Promise logic continues to implement non-preemptive multitasking.

In the example gist you can run the example with a command line parameter that selects blocking or non-blocking CLI input. asyncExample.js