Understand javascript async/await

Writing in the front

Async functions are the syntactic sugar of Generator functions, and more and more people are studying async/await, which is said to be the ultimate solution of asynchronous programming. What exactly does async handle

instructions

Reference for this article:

Understand JavaScript async/await.

JavaScript async/await: The Good Part, Pitfalls and How to Use

How to escape async/await hell

Write simple promises by hand

What are async and await doing

Any name makes sense, so let’s take it literally. Async is short for ‘asynchronous’ and await can be thought of as short for async wait. So it makes sense that async is used to declare that a function is asynchronous and await is used to wait for an asynchronous method to complete. There is also an interesting syntax that says await can only appear in async functions. Then careful friends will have a question, if await can only appear in async function, how should the async function be called? If you want to call an async function with await, you must wrap an async function around the call and then… Into an endless cycle of never coming out… If async functions do not need to be called with await, then what is async really for?

What does async do

The key to this problem is how async handles its return value! We would certainly like it to return the desired value directly via the return statement, but if so, there seems to be no await. So write some code to see what it returns:

async function testAsync() {
  return 'hello async';
}
const result = testAsync();
console.log(result);
Copy the code

When I saw the output, I realized it was a Promise object.

So the async function returns a Promise object. You can get this information from the documentation as well. Async functions (including function statements, function expressions, and Lambda expressions) return a Promise object. If a function returns an immediate Promise, async wraps the immediate Promise object with promise.resolve (). The async function returns a Promise object, so in cases where the outermost layer cannot await the return value, we should of course process the Promise object the way it is: then() chains, like this

testAsync().then(v= > {
  console.log(v); // Outputs Hello async
});
Copy the code

Now, what if async does not return a value? It’s easy to imagine that it returns promise.resolve. Think of Promise as a feature-no wait, so an async function executed without await will execute immediately, return a Promise object, and never block subsequent statements. This is no different than a normal function that returns a Promise object. So the next key point is the await keyword.

Await what exactly is waiting for

“Await” is generally considered to be waiting for an async function to complete. But syntactically, await is an expression that evaluates to a Promise object or some other value (in other words, without special qualification). Since an async function returns a Promise object, await can be used to wait for the return value of an async function — it can also be said to await an async function, but be clear that it is waiting for a return value. Note that await is not just used to wait for promises, it can wait for the result of any expression, so await is actually followed by ordinary function calls or direct quantities. So the following example works perfectly

function getSomething() {
  return 'something';
}
async function testAsync() {
  return Promise.resolve('hello async');
}
async function test() {
  const v1 = await getSomething();
  const v2 = await testAsync();
  console.log(v1, v2);
}
test();
Copy the code

Await await, and then

Await waits for what it is waiting for, a Promise object, or some other value, and then what? I have to say first that await is an operator used to form an expression and the result of an await expression depends on what it is waiting for. If it waits for something other than a Promise object, the result of the await expression operation is what it waits for. If it waits for a Promise object, await is busy and blocks the following code, waiting for the Promise object resolve, and then getting the value of resolve as the result of the await expression. See the word “block” above, panic… Rest assured, this is why await must be used in async functions. Async function calls do not block, and all blocking within them is wrapped up in a Promise object and executed asynchronously.

What async/await is doing for us

As a simple comparison, async encapsulates the return value of a subsequent function (function expression or Lambda) into a Promise object, and await waits for the Promise to complete and returns the result of resolve. Now, for example, using setTimeout to simulate a time-consuming asynchronous operation, let’s see what happens without async/await

function takeLongTime() {
  return new Promise(resolve= > {
    setTimeout((a)= > resolve('long_time_value'), 1000);
  });
}
takeLongTime().then(v= > {
  console.log('got', v);
});
Copy the code

What if we use async/await instead

function takeLongTime() {
  return new Promise(resolve= > {
    setTimeout((a)= > resolve('long_time_value'), 1000);
  });
}

async function test() {
  const v = await takeLongTime();
  console.log(v);
}
test();
Copy the code

Eagle-eyed students have noticed that takeLongTime() is not declared async. In fact, takeLongTime() itself is the returned Promise object, and async is the same with or without it. Another question arises, there is no obvious difference between the two codes in the processing of asynchronous calls (actually the processing of Promise objects), and even more code needs to be written to use async/await, so what are its advantages?

The advantage of async/await is processing then chains

The advantages of async/await are not found in a single Promise chain, but in cases where you need to deal with then chains consisting of multiple promises (interestingly, promises use then chains to solve the problem of multi-layer callbacks, Now optimize it further with async/await). Suppose a business is done in multiple steps, each of which is asynchronous and depends on the results of the previous step. We still use setTimeout to simulate asynchronous operations:

/** * passes the argument n, which indicates the time (milliseconds) at which this function is executed. * The result of execution is n + 200, which will be used for the next step */
function takeLongTime(n) {
  return new Promise(resolve= > {
    setTimeout((a)= > resolve(n + 200), n);
  });
}


function step1(n) {
  console.log(`step1 with ${n}`);
  return takeLongTime(n);
}


function step2(n) {
  console.log(`step2 with ${n}`);
  return takeLongTime(n);
}


function step3(n) {
  console.log(`step3 with ${n}`);
  return takeLongTime(n);
}
Copy the code

Now use the Promise approach to implement all three steps of processing

function doIt() {
  console.time('doIt');
  const time1 = 300;
  step1(time1)
    .then(time2= > step2(time2))
    .then(time3= > step3(time3))
    .then(result= > {
      console.log(`result is ${result}`);
      console.timeEnd('doIt');
  });
}
doIt();
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
/ / doIt: 1507.251 ms
Copy the code

Result is the parameter 700 + 200 = 900 of step3(). DoIt () performed three steps in sequence, 300 + 500 + 700 = 1500 milliseconds, as calculated by console.time()/ console.timeend (). If you implement async/await, it will look like this

async function doIt() {
  console.time('doIt');
  const time1 = 300;
  const time2 = await step1(time1);
  const time3 = await step2(time2);
  const result = await step3(time3);
  console.log(`result is ${result}`);
  console.timeEnd('doIt');
}
doIt();
Copy the code

The result is the same as the previous Promise implementation, but doesn’t this code look a lot cleaner, almost like synchronous code and there’s something cooler about it. Now let’s change the business requirement. It’s still three steps, but each step needs the result of each previous step.

function step1(n) {
  console.log(`step1 with ${n}`);
  return takeLongTime(n);
}
function step2(m, n) {
  console.log(`step2 with ${m} and ${n}`);
  return takeLongTime(m + n);
}
function step3(k, m, n) {
  console.log(`step3 with ${k}.${m} and ${n}`);
  return takeLongTime(k + m + n);
}
Copy the code

I’ll write async/await:

async function doIt() {
  console.time('doIt');
  const time1 = 300;
  const time2 = await step1(time1);
  const time3 = await step2(time1, time2);
  const result = await step3(time1, time2, time3);
  console.log(`result is ${result}`);
  console.timeEnd('doIt');
}
doIt();

// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
/ / doIt: 2907.387 ms
Copy the code

There seems to be no difference from the previous example, except that the execution time is longer. Don’t worry, what would it look like if you implemented it as a Promise?

function doIt() {
  console.time('doIt');
  const time1 = 300;
  step1(time1)
    .then(time2= > {
      return step2(time1, time2).then(time3= > [time1, time2, time3]);
    })
    .then(times= > {
      const [time1, time2, time3] = times;
      return step3(time1, time2, time3);
    })
    .then(result= > {
      console.log(`result is ${result}`);
      console.timeEnd('doIt');
    });
  }
doIt();
Copy the code

Note: The Promise object after wait may result in rejected, so it is better to await the try… In the catch block.

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err); }}// Another way to write it
async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}
Copy the code

Conclusion:

  1. Async means that the function has an asynchronous task, and await means that the following async task needs to wait until the async task is complete before proceeding to the next step
  2. “Await” will cause the current async function to suspend execution, but js is not blocked, JS will execute “await” function first, if the function is asynchronous, then the function will be placed in an asynchronous queue, and js will jump out of async function to perform other tasks first. Therefore, Await expressions must be written in async, otherwise JS does not know where to jump to
  3. When an async function is called, a Promise object is returned. When the async function returns a value, the Promise’s resolve method takes care of passing the value; When async throws an exception, Promise’s Reject method also passes the exception. .
  4. The purpose of async/await is to simplify the synchronous behavior of using multiple Promises and to perform on a set of Promises
  5. The promise mechanism can be explained in detail in my previous blog post on MDN