So far in front-end development, asynchronous problems have experienced the despair of Callback Hell, the normative melee of Promise/Deffered, the invincibility of Generator, and now the acceptance of Async/Await by the public, in which Promise and Async/Await are still active in code. Their perceptions and evaluations have been reversed many times, and they have their own fans, creating a love-hate relationship that continues to this day, and the thoughts and inspirations behind them are still worth pondering.
The goal of this article is not to start a debate, nor to promote either approach as the only best practice for front-end asynchrony, but to explore the hidden consensus underneath the controversy, based on an introduction to the knowledge and anecdotes behind Promise and Async/Await.
Promise is a solution for asynchronous programming that is more sensible and powerful than the traditional callback hell.
A Promise is simply a container that stores the result of a time that will only end in the future. Syntactically, a Promise is an object from which messages for asynchronous operations can be retrieved, and it provides a unified API so that all kinds of asynchronous operations can be handled in the same way. Its internal state is as follows.
The flow between states is irreversible and the code is written as follows.
It is easier to understand from a syntactic point of view, but the beauty is the lack of brevity, the inability to break points and the redundant anonymous functions.
2.1. How is a Promise implemented?
When I first started out, I researched the topic “How to implement a Promise” and tried to write the following code.
Although this code sucks, I have to say that it was quite satisfying at the time, and later it turned out that it could not solve the problem of asynchronous registration.
Those interested in this section can look up the standard implementation for themselves, but the process of exploring it really does evoke an interest in the basics, which is the reason this article went digging into it in the first place.
Next, let’s look at Async/Await.
Async/Await is not a new concept, and indeed it is.
It was formally introduced in 2012 with the release of version 5.0 of Microsoft’s C# language, and then in Python and Scala. After that, the Async/Await specification was formally introduced in ES 2016, which is the subject of our discussion today.
Here is a sample code for using Async/Await in C#.
In fact, there are quite a few Async/Await-like implementations in the front-end domain
3.1. How is Async/Await implemented?
The ES2017 standard introduced the async function to make asynchronous operations more convenient.
Here is a quote from Mr. Yifeng Ruan’s description.
What is the async function? In a nutshell, it’s syntactic sugar for the Generator function.
The preceding section has a Generator function that reads two files in turn.
gen in the above code could be written as an
async function, as in the following.
A quick comparison shows that the
async function replaces the asterisk (
async and the
await, and that’s it.
The main improvements over Generator are focused on.
- Built-in executors
- better semantics
- Promise return values
As you will see here, Async/Await is essentially the syntactic sugar of Promise: the Async function returns a Promise object.
Let’s take a look at the actual Babel transformed code to make it easier to understand
It’s easy to see that the call is eventually converted to a Promise based call, but the original three lines of code are converted to 52 lines of code, which in some scenarios imposes a cost.
For example, Vue3 does not use ? (optional chain operator notation) for the following reasons.
Although the use of ? s simplicity, the actual packaging is more redundant, increasing the size of the package and affecting the loading speed of Vue3, which is a sore point with the simplicity of Async/Await syntax.
Ignoring the deeper runtime performance for the moment, is Async/Await perfect, just in terms of how the code is used?
Take the “request N retries” implementation as an example.
A common implementation idea is to pass the handles of the Resolve and Reject of a Promise into the iteration function to control the internal state transformation of the Promise, but what if you use Async/Await? Obviously not very well, exposing some of its shortcomings.
- Lack of complex control processes, such as always, progress, pause, resume, etc.
- Internal state is not controlled and error catching relies heavily on try/catch
- Lack of interrupt methods and the inability to abort
Of course, some of the requirements may be rare from the EMCA specification’s point of view, but if they were included in the specification, it would reduce the headache for front-end programmers when choosing an asynchronous process control library.
Promise and Async/Await are both excellent solutions for front-end asynchronous processing, but they have some shortcomings. As front-end engineering progresses, there will be better solutions to address these issues, so don’t be disappointed, the future is still looking bright.