1 introduction
As we all know, the JS language is single threaded. In the actual development process, we will face a problem, that is, the synchronization operation will block the whole page and even the whole browser operation, and only after the synchronization operation is completed, other processing can be continued, this kind of synchronous waiting user experience is very poor. So asynchronous programming is introduced in JS, the main feature is not blocking the main thread to continue to execute, users intuitively feel that the page will not be stuck.
2 Concept Description
2-1 Browser processes and threads
First of all, you can make sure that the browser is multi-process, for example, opening multiple Windows may correspond to multiple processes, so that you can ensure that pages do not affect each other, and one page will not affect other pages. Also for the browser process, is multi-threaded, such as our front-end developers most need to understand the browser kernel is the browser rendering process, mainly responsible for page rendering, script execution, event processing and other tasks. In order to introduce the concept of JS single thread, we will briefly introduce several threads commonly used in the browser kernel:
-
The GUI rendering thread is responsible for rendering the browser page, parsing HTML + CSS, building the DOM tree, laying out and drawing the page, and when the page needs to be redrawn or printed back to stream.
-
JS engine thread JS engine, responsible for parsing and running JS scripts, a page is always only a JS thread to be responsible for running JS procedures, this is what we often say JS single thread.
Note: the JS engine thread and the GUI rendering thread are always mutually exclusive, so when our JAVASCRIPT script takes too long to run, or a synchronization request is never returned, the rendering operation of the page will block, which is often said to be dead
-
The event trigger thread accepts the action event response in the browser. For example, when the mouse, keyboard and other events are monitored, if there is an event handle function, the corresponding task will be pushed into the queue.
-
Timed trigger thread Browser model Timed counters are not counted by JavaScript engines, because JavaScript engines are single-threaded and cannot accurately time if they are blocked. They must rely on external timing and trigger timing.
-
If a callback function is set, the asynchronous thread generates a state change event and places the callback in the event queue when XMLHttpRequest detects a state change after a connection and the browser opens a new thread request. It’s executed by the JavaScript engine.
2-2 JS single thread
Because only the JS engine thread is responsible for processing THE JS script, JS is said to be single threaded. It is understandable that js was originally designed as a single-threaded language because JS needs to manipulate the DOM. Multi-threaded execution will introduce a lot of complications, such as one thread deleting the DOM and one thread adding the DOM, which the browser cannot handle. Although js now supports webworker multi-thread, new threads are completely under the control of the main thread, which is used to process a lot of time-consuming computation, and cannot process DOM, so JS is essentially single threaded.
2-3 Synchronous asynchronous
A synchronization task refers to a task that is queued to be executed on the main thread. The next task can be executed only after the first task is completed. Asynchronous tasks are tasks that do not enter the main thread but enter the task queue. The task queue notifies the main thread that an asynchronous task is ready to execute.
2-4 Task queue
Task queues are used to store asynchronous operations with execution queues. In ES6, task queues are divided into macro task queues and micro task queues.
A macroTask queue is an asynchronous task that is distributed by the host environment. The task queue is a first-in, first-out data structure in which the first event is read by the main thread.
A microtask queue is a task distributed by the JS engine and always added to the end of the current task queue for execution. In addition, during the processing of microtasks, new microtasks are added to the end of the queue and executed
2-5 Event cycle mechanism
Once asynchronous times are added to the task queue, how do you control their execution time? Once the JS engine completes all synchronous tasks in the execution stack (the JS engine is idle at this time), the system reads the task queue, adds runnable asynchronous tasks to the executable stack, and starts execution.
ES5 JS event loop reference diagram:
ES6 JS event loop reference diagram:
Understand the basic principle of JS program execution, the following can step into the topic, discuss our actual development, how to write asynchronous program to make their code easy to read and understand bug less.
3 callback
In JavaScript, A callback is defined as if function A is passed as an argument (function reference) to another function B that executes function A. Let’s say function A is called the callback function. If there is no name (function expression), it is called an anonymous callback function.
Therefore, callbacks are not necessarily asynchronous; they are often used in general synchronous (blocking) scenarios, such as when a callback function is executed after some operation is required.
The callback function is widely used in the asynchronous development of JS. The following lists several common callback functions in the development, such as:
- Time delay operation
setTimeout(function(){// this method is a callback method //code}, 1000)setInterval(()=>{// this method is anonymous callback method //code}, 1000)Copy the code
- nodeapi
//node reads fs.readfile (XXX,'utf-8'.function(err, data) {// This method is the callback method after reading the file successfully //code});Copy the code
- Ajax operation
$.ajax({
type: "post",
url: "xxx",
success: function(data){//post request error callback method //code}, error: fucntion(e){//post request error callback method //code}})Copy the code
The advantage of using the callback method for asynchronous development is that it is simple and easy to understand
The disadvantages of the callback function are illustrated with a small example:
method1(function(err, data) {
//code1
method2(function(err, data) {
//code2
method3(function(err, data) {
//code3
method4(D, 'utf-8'.function(err, data) {
//code4
});
});
});
});
Copy the code
If asynchronous methods are executed in a clear sequence, slightly more complex operations can easily be structured like the above example. If business code is added, the program becomes extremely complex and difficult to understand and debug, which is what we call callback hell.
If you want to realize more complex functions, the limitations of the callback function will be highlighted, two asynchronous requests to perform at the same time, for example, when two in performing an operation at the end of the operation, or requests for two at the same time, take priority to perform the complete results, this needs to be in their respective callback method in monitoring status to complete.
With the popularity of the new ES6/ES7 standard, we should look for new asynchronous solutions to replace this traditional callback approach.
4 Promise
ES6 added support for Promise objects, which provide a unified interface to retrieve status information for asynchronous operations and add processing methods that cannot be handled.
Promise objects have only three states:
- Pendding: Initial state, which is neither successful nor failed.
- This is a pity: which means that the operation will be completed successfully.
- Rejected: Indicates that the operation fails.
The state of a Promise can only be changed internally, and only once.
Let’s see if implementing multilevel callbacks using Promise can solve callback hell
function read(filename){
returnNew Promise((resolve, reject) => {// async code, Example: fs.readfile (filename,'utf8', (err, data) => {
if(err) reject(err); resolve(data); }); })}read(filename1).then(data=>{
return read(filename2)
}).then(data => {
return read(filename3)
}).then(data => {
return read(filename4)
}).catch(error=>{
console.log(error);
})
Copy the code
By working with the code, we found that Promise could be used to implement asynchronous functionality just like synchronous code, avoiding the problem of layers of nesting.
How do YOU use Promise to fulfill the need to launch multiple asynchronous operations simultaneously
- The operation is executed after multiple requests have been completed
function loadData(url){
return new Promise((resolve, reject)=>{
$.ajax({
type: "post",
url: url,
success: function(data){resolve(data)}, error: Fucntion (e){// Post request error callback method reject(e)}})})} promise. all([loadData(urL1), loadData(urL2), loadData(url3)]) .then(data => { console.log(data) }).catch(error => { console.log(error); })Copy the code
- The operation is performed when one of the multiple requests completes (successfully or rejected)
function loadData(url){
return new Promise((resolve, reject)=>{
$.ajax({
type: "post",
url: url,
success: function(data){resolve(data)}, error: Fucntion (e){// Post request error callback method reject(e)}})})} promise.race ([loadData(urL1), loadData(urL2), loadData(url3)]) .then(data => { console.log(data) }).catch(error => { console.log(error); })Copy the code
Writing asynchrony with promises avoids callback hell and makes it easy to implement the multiple asynchronous request actions that callback needs to introduce control code to implement.
Of course, Promise has its drawbacks:
- Once a promise is created, it is executed immediately and cannot be canceled
- If you don’t set the callback function, errors thrown internally by a promise won’t be reflected externally
- When in the pending state, there is no way to know the current stage of progress (just started? , coming to an end?
With these drawbacks in mind, move on to other asynchronous programming schemes.
Do you really know how to use Promises
5 Generator
ES6 adds a Generator asynchronous solution that has a completely different syntax from traditional methods.
The Generator function is a state machine that encapsulates multiple internal states and is also a traverser object Generator that traverses each internal state at once.
Generator is declared as function* and can return multiple times with yeild in addition to the normal return.
Call a Generator object to generate a Generator object, but do not execute it. There are two ways to execute a Generator object:
- The next() method executes the generator method. Yeild returns a {value:xx, done: true/fasle} object. Done: true indicates that the generator is complete
- The second method is to use for…. The of loop iterates over the generator object
Generator has many uses, and this article only discusses its ability to use it to pause function execution and return the value of an arbitrary expression to synchronize the expression of asynchronous code. From a dead end point of view we want to achieve this:
functionLoadData (url, data){// Asynchronous request to get datareturn new Promise((resolve, reject)=>{
$.ajax({
type: "post",
url: url,
success: function(data){resolve(data)}, error: fucntion(e){// Reject (e)}})})}function* gen() {
yeild loadData(url1, data1);
yeild loadData(url2, data2);
yeild loadData(url3, data3);
}
for(letData of gen()){console.log(data)}Copy the code
But this is not enough, because asynchronous functions do not return values and must be rewrapped to pass the parameter values. Co.js is an execution library for such a generator. To use it, we just pass our gen to it like this co(gen), yeah.
function* gen() {
let data1 = yeild loadData(url1, data1);
console.log(data1);
let data2 = yeild loadData(url2, data2);
console.log(data2);
letdata3 = yeild loadData(url3, data3); console.log(data3); } co (gen ()). Then (data = > {/ / gen completes}). The catch (err = > {/ / code})Copy the code
Because of the new async/await support in ES7, asynchronous development has a better choice, and you can basically give up writing asynchronous development with native generator, so we just have a simple concept, and we will focus on the final solution of asynchronous programming async/await.
6 async/await
Asycn /await scheme can be said to be the final solution for ASYNCHRONOUS JS programming. Async /await is the syntactic sugar of generator/ CO and also needs to be used with Promise. The main features of the programme are as follows:
- All atomic asynchronous interfaces return promises. A Promise object can perform any asynchronous operation. Resolve () must be used.
- Async functions must be declared before the async keyword. Ordinary functions defined in the function shall be executed and await keyword shall be added before each execution to indicate that the operation needs to wait for the result.
- Execute async functions. The asynch function returns a Promise object, and the Promise object’s then method can be used to specify the next action.
Also use code to illustrate the problem and use the async/await scheme to implement the initial requirements
// A normal functionfunctionLoadData (url){// Asynchronous request to get datareturn new Promise((resolve, reject)=>{
$.ajax({
type: "post",
url: url,
success: function(data){// Post request success callback method resolve(data)}, error: fucntion(e){// POST request error callback method reject(e)}})})} //async function asyncfunction asyncFun(){// Call a normal functionlet data1 = await loadData(url1);
let data2 = await loadData(url2);
letData3 = await loadData(url3)} asyncFun().then(data => {//async function after execution}).catch(err => {// exception fetch});Copy the code
The loadData() function returns a Promise, but await returns the data passed by the normal resole(data) function.
By comparing with the implementation of generator mode, it is better understood that async/await is the syntactic sugar of generator/ CO method, which is exactly the same in terms of function structure. However, the introduction of some external libraries and the encapsulation of some general methods are omitted, which makes the logic of asynchronous development clearer and closer to synchronous development.
After processing sequential requests, here is an example where multiple requests are initiated simultaneously
// A normal functionfunctionLoadData (url){// Asynchronous request to get datareturn new Promise((resolve, reject)=>{
$.ajax({
type: "post",
url: url,
success: function(data){// Post request success callback method resolve(data)}, error: fucntion(e){// POST request error callback method reject(e)}})})} //async function asyncfunction asyncFun(){
await Promise.all([loadData('url1'), loadData('url2')]).then(data => { console.log(data); / / /'data1'.'data2'] }) } asyncFun(); // The race method that works with Promise can also execute any request after completion or exception //async function asyncfunction asyncFun(){
await Promise.race([loadData('url1'), loadData('url2')]).then(data => { console.log(data); })}Copy the code
Best practices
Through the comparison of the above four different asynchronous implementation modes, it can be found that async/await mode is the closest to synchronous development, that is, there is no continuous callback, no continuous call to THEN functions, and no third party library functions are introduced. Therefore, the async/await+ Promise scheme is the best practice scheme at present.
Community and public number published articles, 100% guarantee is our original articles, if there are mistakes, welcome to correct.
The first article in WebJ2EE public number, welcome everyone to pay attention to a wave, let us all learn front-end ~~~
Again, we set up the WebJ2EE public account front end blowing water group, whether we read the article or in the work of the front end of any problems, we can discuss each other in the group, hope to use our experience to help more partners to solve the work and study of confusion, welcome to join.