Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to implement async/await in JavaScript engine

2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces how the JavaScript engine to achieve async/await, has a certain reference value, interested friends can refer to, I hope you can learn a lot after reading this article, the following let the editor take you to understand it.

Preface

We all know that Promise can solve the problem of callback hell very well, but this method is full of Promise's then () method. If the processing flow is complex, then the whole code will be full of then, the semantics is not obvious, the code can not well represent the execution flow, and it is quite complicated to use promise.then. Although the whole request flow has been linearized, the code contains a large number of then functions. Makes the code still not easy to read. For this reason, ES7 introduced async/await, a major improvement in JavaScript asynchronous programming, which provides the ability to access resources asynchronously using synchronous code without blocking the main thread, and makes the code logic clearer.

How the JavaScript engine implements async/await. If you come up and directly introduce how async/await is used, then you may be a little confused, so we will explain it step by step from its bottom technical point to give you a thorough understanding of how async and await work.

First of all, this paper introduces how the Generator works, and then explains the underlying implementation mechanism of Generator-- Coroutine; and because async/await uses Generator and Promise technologies, then we analyze how async/await writes asynchronous code in a synchronous way through Generator and Promise.

Generator VS protocol

The generator function is an asterisked function and can pause and resume execution.

Function* genDemo () {console.log ("start the first paragraph") yield 'generator 2' console.log ("start the second paragraph") yield' generator 2' console.log ("start the third paragraph") yield 'generator 2' console.log ("end of execution") return' generator 2'} console.log ('main 0') let gen = genDemo () console.log (gen.next ( ). Value) console.log ('main 1') console.log (gen.next (). Value) console.log ('main 2') console.log (gen.next (). Value) console.log ('main 3') console.log (gen.next () .value) console.log ('main 4')

Execute the above code and observe the output. You will find that the function genDemo is not executed at once, and the global code and the genDemo function are executed alternately. In fact, this is the characteristic of the generator function, which can pause or resume execution. Let's take a look at how the generator function is used:

Execute a piece of code inside the generator function, and if the yield keyword is encountered, the JavaScript engine returns the contents after the keyword to the outside and pauses the execution of the function.

The external function can resume the execution of the function through the next method.

You must be curious about the principle of pausing and resuming functions, so let's briefly introduce how the JavaScript engine V8 implements the pausing and resuming of a function, which will also help you understand the async/await that will be introduced later.

To understand why functions can be paused and resumed, you must first understand the concept of co-programming. A co-program is a more lightweight presence than a thread. You can think of the co-program as a task running on a thread, there can be multiple co-programs on a thread, but only one co-program can be executed on a thread at the same time, for example, the current execution is A-co-process, to start B-co-process, then A-co-program needs to hand over the control of the main thread to B-co-process, which is reflected in that A-co-process suspends execution and B-co-program resumes execution; similarly, A-co-process can also be started from B-co-process. In general, if the B co-program is started from the A co-program, we call the A co-program the parent of the B co-program.

Just as a process can have multiple threads, a thread can have multiple collaborators. Most importantly, the co-program is not managed by the operating system kernel, but is completely controlled by the program (that is, executed in the user mode). The benefit of this is that performance is greatly improved and does not consume as much resources as thread switching.

In order to give you a better understanding of how the collaborative program is executed, combined with the execution process of the above code, I draw the following "collaborative program execution flow chart", which you can analyze against the code:

From the figure, you can see the four rules of the collaboration process:

A co-program gen is created by calling the generator function genDemo, and the gen protocol is not executed immediately after creation.

For the gen protocol to execute, you need to call gen.next.

When the co-program is executing, you can pause the execution of the gen co-program through the yield keyword and return the main information to the parent co-program.

If the co-program encounters the return keyword during execution, the JavaScript engine ends the current co-program and returns the contents after the return to the parent co-program.

However, for the above code, you may have this question: the parent co-program has its own call stack, and the gen co-program also has its own call stack. When the gen protocol gives control to the parent co-program through yield, how does V8 switch to the parent co-program's call stack? When the parent program resumes the gen program through gen.next, how to switch the call stack of the gen program?

To understand the above problems, you need to pay attention to the following two points.

The first point: the gen protocol and the parent co-program are executed interactively on the main thread, not concurrently, and their previous switching is done through yield and gen.next.

The second point: when the yield method is called in the gen protocol, the JavaScript engine saves the current call stack information of the gen protocol and restores the call stack information of the parent protocol. Similarly, when gen.next is executed in the parent co-program, the JavaScript engine saves the call stack information of the parent co-program and restores the call stack information of the gen protocol.

In order to intuitively understand how the parent and gen protocols switch the call stack

At this point, I believe you have figured out how the co-program works, in fact, in JavaScript, the generator is an implementation of the co-program, so I believe you also understand what the generator is. So next, we use the generator and Promise to modify the initial piece of Promise code. The modified code is as follows:

/ / foo function function* foo () {let response1 = yield fetch ('https://www.geekbang.org') console.log (' response1') console.log (response1) let response2 = yield fetch ('https://www.geekbang.org/test') console.log (' response2') console.log (response2)} / / Code for executing foo function let gen = foo () function getGenPromise (gen) {return gen.next (). Value} getGenPromise (gen). Then ((response) = > {console.log ('response1') console.log (response) return getGenPromise (gen)}) .then ((response) = > {console.log (' response2') console.log (response)})

As you can see from the figure, the foo function is a generator function, which implements asynchronous operations in the form of synchronous code in the foo function; but outside the foo function, we also need to write a code to execute the foo function, as shown in the second half of the above code, so let's analyze how this code works.

The first execution is let gen = foo (), which creates the gen protocol. Control of the main thread is then transferred to the gen co-program by executing gen.next in the parent co-program.

After the gen protocol gains control of the main thread, it calls the fetch function to create a Promise object response1, then pauses the execution of the gen program through yield and returns the response1 to the parent program.

After the parent co-program resumes execution, the response1.then method is called to wait for the result of the request.

After the request initiated through fetch is completed, the callback function in then is called. After the callback function in then gets the result, the callback function in then relinquishes control of the main thread by calling gen.next and transfers the control to the gen co-program to execute the next request.

The above is a general process in which the collaborative process and Promise cooperate with each other. In general, however, we encapsulate the code that executes the generator into a function and call the function that executes the generator code an executor (see the famous co framework), as follows:

Function* foo () {let response1 = yield fetch ('https://www.geekbang.org') console.log (' response1') console.log (response1) let response2 = yield fetch ('https://www.geekbang.org/test') console.log (' response2') console.log (response2)} co (foo ()

By using a generator with an executor, you can write asynchronous code synchronously, which greatly enhances the readability of the code.

Async/await

Although the generator can meet our needs well, the programmer's pursuit is endless, which not only introduces async/await in ES7, but also can completely say goodbye to executors and generators and achieve more intuitive and concise code. In fact, the secret behind async/await technology is Promise and generator applications, which, to a lower level, are micro-tasks and collaborative applications. To understand how async and await work, we have to analyze async and await separately.

Async

Let's take a look at what async is. According to the definition of MDN, async is a function that executes asynchronously and implicitly returns Promise as a result.

Let's first take a look at how to implicitly return Promise. You can refer to the following code:

Async function foo () {return 2} console.log (foo ()) / / Promise {: 2}

By executing this code, we can see that calling the foo function declared by async returns a Promise object with a status of resolved, and the result is as follows:

Promise {: 2} await

We know that the async function returns a Promise object, so let's combine the code in this article to see what await is.

Async function foo () {console.log (1) let a = await 100console.log (a) console.log (2)} console.log (0) foo () console.log (3)

Looking at the above code, can you tell what the printed content is? First, we have to analyze what happens when async is combined with await. Before going into more detail, let's take a look at the overall execution flow chart of this code from the perspective of collaboration:

Combined with the above figure, let's analyze the execution process of async/await.

First, execute the statement console.log (0) and print out 0.

This is followed by the execution of the foo function, because the foo function is marked by async, so when entering the function, the JavaScript engine saves the current call stack and other information, then executes the console.log (1) statement in the foo function, and prints out 1.

Next, we will execute the statement await 100in the foo function, which is the focus of our analysis, because when executing the statement await 100, the JavaScript engine has done too many things for us silently, so let's take the statement apart and see what JavaScript has done.

When executing to await 100, a Promise object is created by default, as shown in the following code

Let promise_ = new Promise ((resolve,reject) {resolve)})

During the creation of this promise_ object, we can see that the resolve function is called in the executor function, and the JavaScript engine submits the task to the micro-task queue.

The JavaScript engine then suspends the execution of the current co-program, transfers control of the main thread to the parent co-program, and returns the promise_ object to the parent co-program.

Control of the main thread has been handed over to the parent co-program, and one of the things the parent co-program does is call promise_.then to monitor the promise state change. Next, we continue the process of executing the parent program, where we execute console.log (3) and print out 3.

After that, the parent co-program will finish execution. Before the end, it will enter the checkpoint of the micro task, and then execute the micro task queue. There are tasks of resolve (100) in the micro task queue waiting for execution. When the task is executed here, the callback function in promise_.then will be triggered, as shown below:

Promise_.then ((value) = > {/ / after the callback function is activated / / transfer the control of the main thread to the foo co-program and pass the vaule value to the co-program})

After the callback function is activated, control of the main thread is handed over to the foo function's co-program, and the value value is passed to the protocol at the same time.

After the foo protocol is activated, the previous value value is assigned to the variable a, and then the foo protocol continues to execute the subsequent statements, and when the execution is complete, the control is returned to the parent program.

The above is the execution process of await/async. It is because async and await do a lot of work behind our backs that we can write asynchronous code in a synchronous way.

Thank you for reading this article carefully. I hope the article "how to achieve async/await in JavaScript engine" shared by the editor will be helpful to everyone. At the same time, I also hope that you will support us and pay attention to the industry information channel. More related knowledge is waiting for you to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report