Definition:

Async functions are syntactic sugar for Generator functions and are very similar in use to Generator functions in order to make use of Promises more smoothly.

Basic usage:

Let’s start with an example where we use Generator functions to perform two asynchronous tasks and one synchronous task in turn. Note that while synchronous tasks are executed first, asynchronous tasks are invoked first.

function p1(){ return new Promise(function(resolve,reject){ resolve('success1'); }) } function p2(){ return new Promise(function(resolve,reject){ resolve('success2'); }) } function* fn1(){ yield p1(); yield p2(); Yield 'This is a synchronous task '; } let fn2 = fn1(); fn2.next().value.then(data => { console.log(data); }) fn2.next().value.then(data => { console.log(data); }) console.log(fn2.next().value); // This is the synchronization task // Success1 // Success2Copy the code

Next we use async and await to accomplish the same function:

function p1(){ return new Promise(function(resolve,reject){ resolve('success1'); }) } function p2(){ return new Promise(function(resolve,reject){ resolve('success2'); })}; async function fn1(){ let res1 = await p1(); let res2 = await p2(); Let res3 = await 'this is synchronization task '; console.log('res1:'+res1); console.log('res2:'+res2); console.log('res3:'+res3); }; fn1(); // RES1: Success1 // RES2: Success2 // RES3: This is a synchronization taskCopy the code

We can see that we no longer need to call the next method repeatedly to execute asynchronous methods as Generator functions do, and only execute the asynchronous function after the last await function after the next await function.

The main differences between Generator functions are as follows:

  • First of all, it has a built-in executor, we no longer need to call each asynchronous method manually, the next one will be automatically executed after the last await is finished.

  • The await command can be followed by more values, which can be Promise objects and primitive values (numeric, string and Boolean values, but will be automatically converted to an immediately resolved Promise object).

  • Async returns a Promise object, while Generator returns an iterable.

So async functions can also be regarded as Promise objects wrapped by multiple asynchronous operations.

Grammar:

  • Return a Promise object: The async function returns a Promsie object anyway, and if we set a return value to it, we can get that value in then, even if the return value we set is a string.

    async function f() { return ‘hello world’; }

    f().then(v => console.log(v)) // “hello world”

If async functions have errors inside, we can use the then method to catch them.

Async function f() {throw new Error(' Error '); } f().then(v => console.log('resolve', v), e => console.log('reject', e)) //reject Error: ErrorCopy the code
  • Promsie object state change: Inside the async function the state of the Promise object we return will become resolved only after all await commands have been executed, unless a return or an error occurs. If we want to perform multiple asynchronous operations, our async function will terminate if one fails, and what if we want other asynchronous operations to continue? We can use try{}catch(){} to catch this error and ensure that the rest of the asynchronous operation executes.

    function p1(){ return new Promise(function(resolve,reject){ // resolve('success1'); reject('error'); }) } function p2(){ return new Promise(function(resolve,reject){ resolve('success2'); })}; async function fn1(){ try{ let res1 = await p1(); let res2 = await p2(); Let res3 = await 'this is synchronization task '; }catch(error) { console.log(error); }}; fn1();Copy the code
  • Execution optimization: In practice it would be a waste of time to execute asynchronous operations simultaneously if we want to execute one operation and then execute the other. We can call the asynchronous operation first and write the return value of the asynchronous operation after the await command:

    let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;

  • **a****sync function can preserve the run stack: ** the error stack includes async if an asynchronous operation inside an async function fails. If it’s a normal function that has an asynchronous operation inside it, the error stack doesn’t include that function.

    const a = () => { b().then(() => c()); };

In the above code, function A runs an asynchronous task b() inside. When b() runs, function A () does not interrupt, but continues execution. By the time b() finishes running, it is possible that A () has long since finished running, and the context in which B () exists has disappeared. If b() or c() reports an error, the error stack will not include a().

Now change this example to an async function.

const a = async () => {
  await b();
  c();
};
Copy the code

In the code above, when b() runs, a() is paused, and the context is saved. If b() or c() reports an error, the error stack will include a().

Several characteristics of asynchronous operations:

If we need to load a lot of data asynchronously in order, if we use Promise, we need to use Map to traverse our request and then use reduce to connect all Promsie via the then method.

If we use async a for loop can be solved and the code is greatly simplified. Async is suitable for sending a large number of requests in sequence.

Function logInOrder(urls) {const textPromises = urls.map(URL => {return fetch(URL). Then (response =>) response.text()); }); TextPromises. Reduce ((chain, textPromise) => { return chain.then(() => textPromise) .then(text => console.log(text)); }, Promise.resolve()); }Copy the code

The code above uses the fetch method to read a set of urls remotely simultaneously. Each fetch operation returns a Promise object into the textPromises array. The Reduce method then processes each Promise object in turn, and then uses then to concatenate all the Promise objects so that the results can be output in turn.

It’s not very intuitive, it’s not very readable. Below is the async function implementation.

async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); }}Copy the code

The above code is really much simpler, but the problem is that all remote operations are secondary. Only when the previous URL returns a result will the next URL be read, which is inefficient and a waste of time. What we need is to make remote requests concurrently.

Async function logInOrder(urls) {// Async function logInOrder(urls) {const textPromises = urls.map(async URL => {const response = await fetch(url); return response.text(); }); // Output for (const textPromise of textPromises) {console.log(await textPromise); }}Copy the code

In the above code, although the map method takes an async function as an argument, it is executed concurrently, because only the async function is executed internally, and the outside is not affected. Behind the for… The of loop uses await inside, so it outputs sequentially.

Reference: Ruan Yifeng ES6