Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does async make everything inside it asynchronous?

According to MDN,

the async function declaration defines an asynchronous function

I understand it as the function will be treated as an async process just like setTimeout or some database request. For example, in the example below, the process should output "main" somewhere between the numbers.

let func2 = async () => {
   for (let i = 0; i < 51; i ++) {
       console.log(i);
   }
}

(async () => {
    func2();
    console.log("main");
})()

However, "main" is always consoled at the end like the whole process is synchronous. What am I understanding wrong?

If the synchronous code is still synchronous, what's the purpose of async? Only allows await inside it and provide some fancy way to return a Promise?

like image 629
zhangjinzhou Avatar asked Jul 06 '18 01:07

zhangjinzhou


2 Answers

Does async make everything inside it asynchronous?

No. It does not. The code in your function still runs synchronously and will block the event loop while it is running.

If the synchronous code is still synchronous, what's the purpose of async? Only allows await inside it and provide some fancy way to return a Promise?

Yes, it is primarily for await.

But, there are also some times when the fact that it automatically wraps your code with a promise is useful. For example, it automatically catches any thrown exceptions and turns them into a rejected promise. Sometimes that is useful. For an example of this other usefulness, see the "Error Handling" section of this article: 6 Reasons Why JavaScript’s Async/Await Blows Promises Away.

But, the primary function of the async keyword is to define a function in which you can use await. This is how the designers of ES7 decided to make await work.

FYI, if there's an await in the function that awaits a promise, then that will cause the function to return early at the point of the await. It will return at that point and return a promise. The operation that you are awaiting will have started and the rest of the function will not execute until after the awaited promise is resolved. So, using an await makes some of the code get executed later. But, as with the code before the await, even when the function resumes running after the await has resolved, your Javascript in that function is still synchronous and blocking (until another await or until a return).

Wrapping it in a promise and detecting when it's done with .then() will postpone when the .then() handler is called until the next tick of the event loop. So, then .then() will be slightly delayed because you wrapped it in a promise and then used .then(), but the timing of when your code runs does not change. It is still synchronous and blocking.

In Javascript, the only ways to take synchronous code and make it asynchronous are:

  1. Run it in another process and use interprocess communication to communicate back the results.

  2. Rewrite your code in a native code add-on that uses native threads or some other OS asynchronous interface to do the actual work and then offer an asynchronous interface to Javascript from your add-on (usually an interface that returns a promise or notifies completion via a callback). This is now node.js asynchronous functions such as fs.readFile() work. They have a native code implementation that returns immediately, then uses native threads in the implementation and notifies completion via a callback.

  3. Use some node.js add-on that does some of this for you. There are add-ons that purport to offer threads.

  4. Use node.js threads in node version 10.5+ and program with the restrictions that they require. Here's an article on the topic: Threads in Node 10.5.0: a practical intro

like image 101
jfriend00 Avatar answered Nov 14 '22 00:11

jfriend00


No, the body inside the async function runs when it is called but pauses for await expressions. If there is nothing to await, the function will just run like a normal function with the exception that it returns a promise.

However if there is an await, execution is paused and the event loop continue. When the awaited promise resolves sometime after the current event loop has finished, it picks up where it left off. For example compare when "main" is logged here vs. your example.

let func2 = async () => {
    console.log("Start")
    for (let i = 0; i < 10; i ++) {
        if(i == 5) {
            await new Promise(resolve => setTimeout(resolve, 200))
        }
        console.log(i);
    }
 }
 
 (async () => {
     func2().then(() => console.log('done'));
     console.log("main");
 })()

Another way to think about async functions is to consider their relationship to generators. I like to think of await as a sort of yield. For example the only thing we need to change in the body of func2 to get the same behavior is swapping await for yield:

function* func2() {
  console.log("Start")
  for (let i = 0; i < 10; i++) {
    if (i == 5) {
      yield new Promise(resolve => setTimeout(resolve, 0))
    }
    console.log(i);
  }
  return "done"
}

let gen = func2()
gen.next().value.then(() => console.log(gen.next().value))

console.log("main")

async/await just makes this easier and a little more intuitive for common use cases.

like image 39
Mark Avatar answered Nov 14 '22 00:11

Mark