This article focuses on the implementation of async/await. Although async/await has been used in the past, the scene is relatively simple and I only have a general understanding of it. Recently, I made a node service project that uses async/await a lot, causing a lot of confusion in the details of use. Such as:

Confusion about exception handling

Await promises can handle exceptions by either enclosing a try catch or adding.catch() to the Promise. Which one should we use?

Why can’t we catch an exception inside the _run method if we don’t declare await when the following async function is executed?

async run () {
  try {
    await _run()
  } catch (err) {} 
}
Copy the code

What exceptions can an async try catch catch? Why is it better than Promise’s exception handling?

Confusion about async functions

What happens to async functions if I do not use await, or if I add async definition to a normal function, can I use it as before?

Async functions that return ‘great’ are equivalent to returning a Promise that resolve is great, The actual await value is still great, not the Promise. Why is that? What if the return is a Promise that has not yet resolved? What’s the principle here?

Implementation principle of Async/await

With these questions in mind and some searching, I gradually understood how it worked and why it was often said that it was just syntactic candy for Promise, when in fact it was an asynchronous flow with the help of a generator to express Promise more clearly. This answer illustrates this point. This draft of TC39 is the material that I think can explain the principle of async/await execution more clearly. Here is the code:

function spawn(genF, self) {
    return new Promise(function(resolve, reject) {
        var gen = genF.call(self);
        function step(nextF) {
            var next;
            try {
                next = nextF();
            } catch(e) {
                // finished with failure, reject the promise
                reject(e);
                return;
            }
            if(next.done) {
                // finished with success, resolve the promise
                resolve(next.value);
                return;
            }
            // not finished, chain off the yielded promise and `step` again
            Promise.resolve(next.value).then(function(v) {
                step(function() { return gen.next(v); });
            }, function(e) {
                step(function() { return gen.throw(e); });
            });
        }
        step(function() { return gen.next(undefined); });
    });
}
Copy the code

To understand this code, you need to be familiar with the features of Generator. This section does not cover the details. This function converts the async function into a new function that will return the Promise. The Promise execution process will automatically cycle the original function as a generator. The three key points of execution are as follows:

  1. Every time next is executed, a try catch is wrapped around it to ensure that an exception is thrown.
  2. At the end of next. Done, the overall Promise is resolved with the generator return value.
  3. Resolve convert the value of await to a Promise via promise.resolve and suspend the callback for it, continue to execute generator on success and make generator throw exception on failure. This is the most important part.

I encourage you to understand the code, try to answer the first few questions for yourself, and then move on to my explanation.

Exception handling:

  • First of all,.catch is something Promise itself supports and we can handle it without any problem, but since we are both using async/await it is better to keep the style consistent and use try catch.

  • Second question, why can’t I catch an exception inside the _run method without declaring an await? Resolve (next.value).then (‘ await ‘). Resolve (‘ await ‘).then (‘ Promise ‘) The next. Value is a Promise returned by _run(), so that _run() is rejected and then step(function() {return gen.throw(e); }) so an exception is caught.

  • Third question, why is it better than Promise’s exception handling? From the prototype code above, we can see that async can be called by try {next = nextF(); } catch common exceptions such as syntax errors in code at all levels, It is also possible to catch await Promise rejected by gen.throw(e) in the onRejected callback of promise.resolve (next-value). Then. Exceptions result in async functions being rejected as a whole as a Promise. A try catch wrapped around a Promise does not catch ordinary exceptions in the THEN callback chain.

About async functions:

  • First question, if I add async definition to a normal function, can I still use it as before? If async returns a Promise, it will return a Promise. If async returns a Promise, you’ll probably still be able to use it.

  • If (next.done) {resolve(next.value)} is a Promise. If (next.done) {resolve(next.value)} is a Promise. If x is a Promise, the callback of A will be attached to x. We await a as await x.

The key is Promise

In order to understand The above content, it is important to understand The principle of Promise first. If you are not clear, it is recommended to take a closer look at The Promise specification, especially The part that defines THEN and The Promise Resolution Procedure. The specification for Then defines that the Then method must return a promise, and the statement behind await is the onFullfilled in the Then method, so async will return a new promise, The promise’s behavior follows this specification as well. The Promise Resolution Procedure specifies how to handle The resolve value, which depends on many details, such as The async function return value mentioned above. And the v8 team’s revamp of async/await, which will be mentioned below. Async /await is easy to understand once these two parts of the specification are clear.

V8 Blog about async/await

A valuable article on the Internet about async/await is the improvement of async/await made by V8 team. It mainly introduces the improvement of async/await implementation in 2018, which reduces the packaging and delivery process of Promise. Resolve (p) saves two microticks by changing new Promise(res => res(p)) to promise.resolve (p). In addition to this improvement, the process of creating a throw Away Promise is reduced, that is, not returning a new Promise on then, saving the time needed to create the Promise, but it does not reduce microTick. After this improvement, await is more in line with people’s intuitive expectation in terms of execution order.

A little thought of mine

From the six reasons Async/Await beat Promise a few years back to how to avoid Async/Await hell, the Async/Await discussion continues. Personally I feel Async/await is a better tool, it makes complex asynchronous logic with multiple nested layers more intuitive and clear, but it puts more demands on the code writer, and it can be easier to write bugs without sufficient knowledge.