Async functions are the ultimate solution to asynchronous callback hell, but understanding generators and promises helps you understand how Async functions work. Because the content is more, it is divided into three parts. This is the third part, which introduces Async function correlation. Part one covers the Generator, and Part two covers promises.

In this part, we will first introduce the basic use of Async function, and then implement an Async function combining the generator and Promise introduced in the previous two parts.

1) Overview of Async functions

1.1 concept

Async functions can be defined by prefixing the async operator to normal functions:

// This is an async function
async function() {}
Copy the code

The code in the Async body is executed asynchronously and does not block subsequent code execution, but is written similarly to synchronous code.

The Async function returns a completed promise object and is actually used with the await operator. Before we get to await, let’s look at some of the features of the Async function itself.

1.2 Basic Usage of Async Functions

1.2.1 No await in function body

If an async function does not have an await operator, then the state it returns depends on how the code inside the async function is written, in the same way as the promise’s then() method does:

1) No explicit return of any data

Promise.resolve() is returned by default:

var a = (async () = >{}) ();Copy the code

The equivalent of

var a = (async () = > {
  return Promise.resolve(); }) ();Copy the code

Now the value of A is:

a {
  [[PromiseStatus]]: 'resolved',
  [[PromiseValue]]: undefined
}
Copy the code

2) Explicit return is not a promise

Resolve (data)

var a = (async () = > {
  return 111; }) ();Copy the code

The equivalent of

var a = (async () = > {
  return Promise.resolve(111); }) ();Copy the code

Now the value of A is:

a {
  [[PromiseStatus]]: 'resolved',
  [[PromiseValue]]: 111
}
Copy the code

3) Explicitly return the promise object

At this point, the state of the promise object returned by the async function is determined by the state of the returned Promise object displayed. Here, the rejected promise is taken as an example:

var a = (async () = > Promise.reject(111()));Copy the code

Now the value of A is:

a {
  [[PromiseStatus]]: 'rejected',
  [[PromiseValue]]: 111
}
Copy the code

In practice, however, we will not use it as above, but with the await operator, otherwise, as above, there will be no advantage over promise. In particular, without the await operator, we cannot use async functions to solve requests for asynchronous data that depend on each other.

In other words: We don’t care about the promise state returned by async (in general, async does not return anything, promise.resolve () is returned by default), we care about the code inside the async function. Because the code inside can be executed asynchronously and does not block the execution of code following async functions, it is possible to write asynchronous code in the same form as synchronous code.

1.2.2 await to introduce

The await operator can be used as follows:

[rv] = await expression;
Copy the code

Expression: Can be any value, but is usually a promise;

The rv: optional. If there is and expression is not a promise value, rv is equal to expression itself; Otherwise, rv is equal to the value of the fulfilled promise, and if the promise is rejected, an exception is thrown (so await is generally wrapped in try-catch, and the exception can be caught).

But note that await must be used in async functions, otherwise it will report a syntax error.

1.2.3 await use

Take a look at the following code example:

1) Expression is not a promise

(async() = > {const b = await 111;
  console.log(b); / / 111}) ();Copy the code

Return the value of expression directly, that is, print 111.

2) expression

(async() = > {const b = await Promise.resolve(111);
  console.log(b); / / 111}) ();Copy the code

Returns the value of the fulfilled promise, so print 111.

3) Expression is a promise to refuse

(async() = > {try {
    const b = await Promise.reject(111);

    // After the preceding await error, the code following the current block will not execute
    console.log(b); / / does not perform
  } catch (e) {
    console.log("Error:", e); // Error: 111
  }
})();
Copy the code

If the promise following an await is rejected or the code itself fails to execute, an exception will be thrown and caught, and the code following the same block as the current await will not execute.

2) Async functions handle asynchronous requests

2.1 Interdependent asynchronous data

In Promise, we used a chain-call approach to handle interdependent asynchronous data, which was much better than the callback function, but still less intuitive to write and understand than synchronous code. Let’s see how async solves this problem.

Let’s review the requirements and promise solutions:

Requirement: request URL1 to get datA1; URL2 = data1[0]. URL2; Request URL3 to get data3, but URL3 = data2[0].url3.

Using the promise chain-call, you can write code like this:

PromiseAjax is an Ajax GET request wrapped through a promise, as defined in 3.1 in Part 2 when we introduce promises.

promiseAjax('URL1')
  .then(data1= > promiseAjax(data1[0].url2))
  .then(data2= > promiseAjax(data2[0].url3);)
  .then(console.log(data3))
  .catch(e= > console.log(e));
Copy the code

Async can be written as if it were synchronous code:

async function() {
  try {
    const data1 = await promiseAjax('URL1');
    const data2 = await promiseAjax(data1[0].url);
    const data3 = await promiseAjax(data2[0].url);
  } catch (e) {
    console.log(e); }}Copy the code

This works because the code following the currently await promise will only execute (or throw an error, and the rest of the code will not execute and go straight to the catch branch) after the promise of the currently await promise is fulfilled.

Here are two things to watch:

1) await helps us deal with promises, either returning an honoured value or throwing an exception; 2) await the whole async function suspends while waiting for the promise to be fulfilled and re-executes the following code after the promise is fulfilled.

For point 2, are generators in mind? In Section 1.4 we’ll write an async function ourselves via generator + Promise.

2.2 Asynchronous data without dependencies

The Async function does notPromise.all()We need to write more async functions.

Promise.all() can be used to process multiple non-dependent asynchronous data in parallel in the same async function, as follows:

async function fn1() {
  try {
    const arr = await Promise.all([
      promiseAjax("URL1"),
      promiseAjax("URL2"),]);// ... do something
  } catch (e) {
    console.log(e); }}Copy the code

Thanks @Jia Shunming for your comment

However, in actual development, if the data requested by the asynchronous request is not relevant to the service, it is not recommended for the following reasons:

Putting all asynchronous requests into one async function manually reinforces the coupling of business code, leading to two problems:

1) It is not intuitive to write code and get data, especially when there are many requests; 2) Promise.all contains multiple asynchronous requests without dependencies. If one of them is rejected or an exception occurs, the result of all requests will not be obtained.

If the business scenario is not concerned with the above two points, consider using the above notation; otherwise, each asynchronous request is issued in a different async function.

Here is an example of writing separately:

async function fn1() {
  try {
    const data1 = await promiseAjax("URL1");

    // ... do something
  } catch (e) {
    console.log(e); }}async function fn2() {
  try {
    const data2 = await promiseAjax("URL2");

    // ... do something
  } catch (e) {
    console.log(e); }}Copy the code

3) Async simulation implementation

3.1 Principle of Async function processing asynchronous data

Let’s take a look at the principle of async:

  • Async function encounteredawaitOperators hang;
  • awaitThe async function is suspended until the following expression is evaluated (usually a time-consuming asynchronous operation) to avoid blocking the code following the async function.
  • awaitAfter the following expression is evaluated (the asynchronous operation is done),awaitYou can do something with this value: return it if it is not a promise; If it is promsie, the value of the Promise is extracted and returned. The async function is told to execute the following code;
  • Where an exception occurs, terminate async.

The asynchronous operation after await tends to return a promise object (such as AXIos) and give it to await. After all, async-await is designed to solve callback hell when requesting data asynchronously, and using promise is a key step.

Async functions themselves behave like generators; An await is usually a promise object, and for this reason async functions are often said to be syntactic sugar combined with generator and promise.

Now that we know the principle of async function processing asynchronous data, we will simply simulate the implementation process of async function.

3.2 Simple implementation of Async functions

This only simulates the scenario where async functions are processed with await network requests, and the request eventually returns a promise object, while async functions themselves return values (completed Promise objects) and more usage scenarios are not considered here.

So the following myAsync function is just to illustrate the async-await principle, do not use it in production.

3.2.1 Code Implementation

Secrets of the JavaScript Ninja (Second Edition), p159 */
// Take the generator as an argument. It is recommended to move to the back and take a look at the generator code
var myAsync = generator= > {
  // Notice that iterator.next() returns the value of the object as promiseAjax(), a promise
  const iterator = generator();

  // Handle controls the suspension - execution of async
  const handle = iteratorResult= > {
    if (iteratorResult.done) return;

    const iteratorValue = iteratorResult.value;

    // Consider only asynchronous requests that return a promise
    if (iteratorValue instanceof Promise) {
      // Recursively call handle, and iterator.next() after the promise is fulfilled to continue the generator execution
      // if (')' then (')')
      iteratorValue
        .then(result= > handle(iterator.next(result)))
        .catch(e= >iterator.throw(e)); }};try {
    handle(iterator.next());
  } catch (e) {
    console.log(e); }};Copy the code

3.2.2 use

MyAsync receives a generator as an input parameter. The code inside the generator function is similar to writing native async functions, except that yield is used instead of await

myAsync(function* () {
  try {
    const a = yield Promise.resolve(1);
    const b = yield Promise.resolve(a + 10);
    const c = yield Promise.resolve(b + 100);
    console.log(a, b, c); // output 1,11,111
  } catch (e) {
    console.log("Error:", e); }});Copy the code

It will print 1, 11, 111.

If the promise after the second yield statement is rejected promise.reject (a + 10), an error is printed: 11.

3.2.3 description:

  • The myAsync function takes a generator as an argument that controls the generator’shangThe entire myAsync function can be reached during the asynchronous code request processhangThe effect of;
  • MyAsync function is defined internallyhandleFunction that controls the generatorSuspend – Execute.

The specific process is as follows:

1) First call generator() to generate its controller, the iterator, while the generator is suspended; 2) The first call to handle and pass iterator.next() completes the generator’s first call; 3) Execute the generator, and when the yield generator suspends again, pass the yield expression’s result (an incomplete promise) to Handle; 4) While the generator is suspended, the asynchronous request is still going on. After the asynchronous request completes (the promise is fulfilled), iteratorValue.then() in handle will be called; 5) When iteratorValue.then() is executed, handle is recursively called internally, and the data returned by the asynchronous request is passed to the generator (iterator.next(result)), which updates the data and executes again. If something goes wrong, it ends; 6) Steps 3, 4, and 5 are repeated until the generator ends, iteratorresult. done === true, and myAsync ends the call.

If you don’t understand, see part 1 about generators and Part 2 about promises.

reference

[1]JOHN RESIG,BEAR BIBEAULT and JOSIP MARAS (2016), Secrets of the JavaScript Ninja (Second Edition), P159, Manning Publications Co. [2] Async function-mdn [3] await-mdn [4] Understand JavaScript async/await