In our work and study, everywhere is full of asynchronous figure, exactly what is asynchronous, what is asynchronous programming, why to use asynchronous programming, and what are the classic asynchronous programming, and what are the scenes in the work, we go a little deeper to learn.
What is asynchronous programming?
It’s important to understand what asynchronous programming is and why.
Let’s start with the concept of asynchrony versus synchronization. Before introducing asynchrony, we should recall that synchronous programming is when the computer executes the code line by line, and the execution of the current code task will block the execution of subsequent code.
Synchronous programming is a typical request-response model in which a request calls a function or method, waits for its response to return, and then executes subsequent code.
Under normal circumstances, the synchronous programming, code executed in sequence in order, can be a very good guarantee the execution, but in some scenarios, such as read from the file content, or request the server interface data, need to perform follow-up operations, according to the data returned to read a file and request interface until the data return this process takes time, the network the worse, the longer the time consuming.
If implemented synchronously, JavaScript can’t handle other tasks while waiting for data to return, and page interaction, scrolling, and other operations will be blocked, which is obviously very unfriendly and unacceptable, and this is where asynchronous programming comes in.
We want to render pages using Ajax requests for data, which is a common way to render pages in our front end. This happens on almost every page. What if the page is requested synchronously here? The browser is locked and no other operations can be performed. And every time a new request is sent, the browser locks up and the user experience is terrible.
Synchronous execution in the browser will look like this, task 2 will not be done until task 1 is done, task 3 will not be done until task 2 is done. This reflects the sequential characteristics of synchronous programming. One, two, three, not one, three, two. But there can be multitasking in our code logic. In our lives, cooking and boiling water can be done at the same time, and we need the same logic in programming.
In computers, we have the concept of multithreading, what does that mean, each thread doing one thing, like this.
Different tasks can be performed in different threads.
But our JavaScript is single-threaded, and by single-threaded, we mean single-threaded. There are thread pools later, but the concept of threads and thread pools is not covered here. If you want to know about it, you can look at books about operating systems.
JavaScript language execution environment is single thread, single thread in the program execution, the path of the program in accordance with the sequential order down, the first must be processed, the next will be executed.
But we also need something like multithreading. But JavaScript is still single-threaded, and we need to execute asynchronously, which causes multiple tasks to execute concurrently.
Parallelism and concurrency. As mentioned above, multi-threaded tasks can be executed in parallel, while JavaScript single-threaded asynchronous programming can achieve multi-task concurrent execution. It is necessary to explain the difference between parallelism and concurrency.
Parallel refers to the simultaneous operation of multiple tasks at the same time. Simultaneous cooking of food and boiling of water means that multiple tasks are carried out simultaneously at the same time, but only one task is carried out at the same time. Drink water while eating. Only drink and eat at the same time.
Let’s talk about asynchrony
Concurrency model
So far, we’ve seen that JavaScript executing an asynchronous task doesn’t need to wait for a response to return, can continue to perform other tasks, and when the response returns, it gets notified and executes a callback or event handler. So how exactly does all this work, and in what rules or order? Now we need to answer this question. Callbacks and event handlers are essentially the same, just different names for different situations.
As mentioned earlier, asynchronous JavaScript programming allows multiple tasks to be executed concurrently, which is based on the fact that JavaScript has a concurrency model based on event loops.
Stack and queue
Before introducing the JavaScript concurrency model, let’s briefly explain the difference between stacks and queues:
Heap: an unblocked area of memory, usually storing objects (reference types);
Stack: Stores data structures in last-in, first-out order, usually storing function parameters and primitive-type value variables (accessed by value);
Queue: Stores data structures in first-in, first-out order.
EventLoop: A JavaScript engine is responsible for parsing and executing JavaScript code, but it doesn’t run on its own. It usually has to have a host environment, such as a browser or Node server. An EventLoop is a mechanism that calls a JavaScript engine to dispatch and execute multiple blocks of JavaScript code (yes, JavaScript code executes in blocks).
There are two constructs that exist in a JavaScript execution environment that you need to know about:
Messagequeue (also called taskqueue) : stores messages to be processed and their corresponding callback functions or event handlers;
Executioncontextstack, also called executioncontextstack: A JavaScript execution stack, as its name implies, consists of an execution context. When a function is called, an execution context, usually called an execution stack frame, is created and inserted, which stores function parameters and local variables. When the function finishes executing, the execution stack frame pops up.
Note: With regard to global code, since all code is executed in the global context, it is easy to understand that the top of the execution stack is always the global context, until all code is executed, the global context exits the execution stack, and the stack is empty. That is, the global context is first on the stack and last on the stack.
task
Before analyzing the event loop process, two concepts are introduced to help you understand the event loop: synchronous and asynchronous tasks.
Task is easy to understand, JavaScript code execution is to complete the task, the so-called task is a function or a code block, usually divided by function or purpose, such as to complete an addition calculation, to complete an Ajax request; There are naturally synchronous and asynchronous tasks. The synchronization task is continuous and blocked; Asynchronous tasks, on the other hand, are discontinuous, non-blocking, contain asynchronous events and their callbacks, and when we talk about executing an asynchronous task, we usually mean executing its callback function.
Event loop flow
The event cycle process is decomposed as follows:
When the host environment creates threads for JavaScript, it creates heap and stack, where JavaScript objects are stored and execution context is stored.
When the asynchronous task is executed, the asynchronous task enters the wait state (not pushed) and notifies the thread: when the event is triggered (or the asynchronous operation response is returned), an event message needs to be inserted into the message queue.
When an event is emitted or a response is returned, the thread inserts the event message (including the event and the callback) into the message queue.
When the synchronization task in the stack is completed, the thread takes out an event message from the message queue, and the corresponding asynchronous task (function) is pushed into the stack to execute the callback function. If the callback is not bound, the message will be discarded and the task will be pushed back after execution.
When the thread is idle (i.e., when the stack is emptied), it continues to pull the next round of messages in the message queue (nextTICK).
The usage code can be described as follows:
vareventLoop = [];
var event;
var i = eventLoop.length – 1; // Last in first out
while(eventLoop[i]) {
event = eventLoop[i–];
If (event) {// The event callback exists
event();
}
// Otherwise the event message is discarded
}
One thing to note here is that waiting for the next event message is synchronous.
Concurrency model and event loop
varele = document.querySelector(‘body’);
function clickCb(event) {
console.log(‘clicked’);
}
function bindEvent(callback) {
ele.addEventListener(‘click’, callback);
}
bindEvent(clickCb);
For the above code we can build the following concurrency model:
As shown in the figure above, when the stack synchronous code blocks are executed successively until the asynchronous task is met, the asynchronous task enters the waiting state and notifies the thread. When the asynchronous event is triggered, an event message is inserted into the message queue. When the subsequent synchronization code of the execution stack is completed, the message queue is read and a message is obtained. Then the corresponding asynchronous task of the message is pushed onto the stack and the callback function is executed. An event loop is completed, and an asynchronous task is processed.
How many asynchronous JS are there?
JS asynchronous operation is quite a lot, the common points are as follows:
setTimeout(setInterval)
AJAX
Promise
Generator
setTimeout
setTimeout(
function() {
console.log(“Hello!” );
}, 1000);
SetTimout (setInterval) is not executed immediately, this code means that after 1s, the function is added to the queue, and if there are no other tasks in the queue, the output ‘Hello’ is executed.
varouterScopeVar;
helloCatAsync();
alert(outerScopeVar);
functionhelloCatAsync() {
setTimeout(function() {
outerScopeVar = ‘hello’;
}, 2000);
}
The outerScopeVar output is undefined instead of Hello. The reason for this is that a value returned in asynchronous code cannot be used in a synchronous process because console.log(outerScopeVar) is synchronous code and setTimout is not executed until it is finished.
helloCatAsync(function(result){
console.log(result);
});
functionhelloCatAsync(callback) {
setTimeout(
function() {
callback(‘hello’)
}
, 1000).
}
Pass a callback, and the console output will be Hello.
AJAX
varxhr = new XMLHttpRequest();
xhr.onreadystatechange= function() {
if ((xhr.status >= 200 && xhr.status < 300) ||xhr.status == 304 ) {
console.log(xhr.responseText);
} else {
console.log( xhr.status);
}
}
xhr.open(‘GET’,’url’, false);
xhr.send();
In this code, the third parameter in xhr.open defaults to false for asynchronous execution, or true for synchronous execution.
Promise specification brief
A promise is an object or function that has then methods. A promise has three states: “Rejected” and “Resolved”. Once a promise is determined, it cannot be changed. The promise state can only be changed from “Rejected” to “Resolved”.
The first callback function of the THEN method is called when the Promise executes successfully, and the second callback function is called when the promise fails. A Promise instance will have a THEN method that must return a new Promise.
Basic usage
// Asynchronous operations are placed in the Promise constructor
constpromise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(‘hello’);
}, 1000);
});
// The operation after the asynchronous result is obtained
promise1.then(value=> {
console.log(value, ‘world’);
},error =>{
console.log(error, ‘unhappy’)
});
Asynchronous code, synchronous writing
asyncFun()
.then(cb)
.then(cb)
.then(cb)
With this chain-writing, Promise solves the callback hell that comes with handling multiple asynchronous nesting, making the code much easier to read, while essentially using the callback function.
Exception handling
If there is an exception in an asynchronous callback, it will not be caught because the callback is executed asynchronously. Let’s see how Promise solves this problem.
asyncFun(1).then(function(value) {
Throw new Error(‘ Error ‘);
},function (value) {
console.error(value);
}).then(function(value) {
},function (result) {
Console. log(‘ error ‘,result);
});
Trycatch = trycatch = trycatch = trycatch = trycatch = trycatch = trycatch = trycatch = trycatch
Promise.prototype.then= function(cb) {
try{
cb()
}catch (e) {
// todo
reject(e)
}
}
An exception thrown in the then method is caught by the second argument to the next cascading THEN method (if there is one), so what if there is an exception in the last THEN as well?
Promise.prototype.done= function (resolve, reject) {
this.then(resolve, reject).catch(function (reason) {
setTimeout(() => {
throw reason;
}, 0);
});
};
asyncFun(1).then(function (value) {
Throw new Error(‘then resolve ‘);
}).catch(function (error) {
console.error(error);
Throw new Error(‘catch ‘);
}).done((reslove, reject) => {});
We can add a done method, which does not return a promise object, so there is no link after that. The done method finally throws the exception globally, so it can be caught by the global exception handler or interrupt the thread. This was also a best practice strategy for Promise, but of course the done approach was not implemented by ES6, so we had to implement it ourselves without using third-party Promise open source libraries. Why do we need this done method.
constasyncFun = function (value) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(value);
}, 0);
})
};
asyncFun(1).then(function(value) {
Throw new Error(‘then resolve ‘);
});
(6312) node: UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1) : Error: then resolve back to bring up the wrong
(node:6312)[DEP0018] DeprecationWarning: Unhandled promise rejections aredeprecated. In the future, promise rejections that are not handledwill terminate the Node.js process with a non-zero exit code
We can see that the JavaScript thread just sends a warning and does not abort the thread. If it is a serious error, it can cause damage if the thread is not aborted in time.
limited
A limitation of a promise is that it cannot terminate the promise chain. For example, when an error occurs in a link of the promise chain, there is no need to continue the execution, but the promise does not provide a native cancellation method. We can see that even if an exception has been thrown before, But the promise chain doesn’t stop. We can abort the promise chain by returning a promise in a pending state.
constpromise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(‘hello’);
}, 1000);
});
promise1.then((value)=> {
Throw new Error(‘ Error! ‘);
}).then(value=> {
console.log(value);
},error=> {
console.log(error.message);
return result;
}).then(function() {
The console. The log (‘ DJL’s xiao);
});
Special scenario
When we have a task that depends on multiple asynchronous tasks, we can use promise.all. When we have a task that depends on any one of multiple asynchronous tasks, it doesn’t matter which one, promise.race
All of the above are ES6 promise implementations, which are actually quite feature-free and somewhat inadequate, so there are many open source promise implementation libraries like Q.js that offer more syntactic sugar and more scenarios to adapt to.
The core code
vardefer = function () {
var pending = [], value;
return {
resolve: function (_value) {
value = _value;
for (var i = 0, ii = pending.length; i < ii; i++) {
var callback = pending[i];
callback(value);
}
pending = undefined;
},
then: function (callback) {
if (pending) {
pending.push(callback);
} else {
callback(value);
}
}
}
};
When calling THEN, all callbacks are queued. When calling resolve, all callbacks are queued and executed
varref = function (value) {
if (value && typeof value.then === “function”)
return value;
return {
then: function (callback) {
return ref(callback(value));
}
};
};
This section of code to achieve the cascade function, the use of recursion. If a promise is passed, the promise is returned directly, but if a value is passed, the value is wrapped as a promise.
Here’s an example of promise combined with Ajax:
functionajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if ((xhr.status >= 200 && xhr.status < 300) ||xhr.status == 304 ) {
resovle(xhr.responseText);
} else {
reject( xhr.status);
}
}
xhr.open(‘GET’, url, false);
xhr.send();
});
}
ajax(‘/test.json’)
.then(function(data){
console.log(data);
})
.cacth(function(err){
console.log(err);
});
generator
Basic usage
function* gen (x) {
const y = yield x + 2;
// console.log(y); // Guess what value will be printed
}
constg = gen(1);
console.log(‘first’,g.next()); //first { value: 3, done: false }
console.log(‘second’,g.next()); // second { value: undefined, done: true }
The yield keyword cedes execution rights to the function, the next method cedes execution rights to the function, and the next method returns external data to the variable to the left of yield in the generator. This enables a two-way flow of data.
The generator implements asynchronous programming
Let’s see how generator implements an asynchronous program (*)
constfs = require(‘fs’);
function* gen() {
try {
const file = yield fs.readFile;
console.log(file.toString());
} catch(e) {
Console. log(‘ exception caught ‘,e);
}
}
/ / actuator
constg = gen();
g.next().value(‘./config1.json’,function (error, value) {
if(error) {
G.row (‘ file does not exist ‘);
}
g.next(value);
});
As you can see, the code in the Generator function feels synchronous, but the process of executing the seemingly synchronous code is complicated, i.e. the process management is complicated.
Extension:
The difference between asynchronous programming and multithreading
Thing in common:
Both asynchrony and multithreading can improve software responsiveness by avoiding blocking the calling thread
Difference:
- Thread is not a function of computer hardware, but a logical function provided by the operating system. Thread is essentially a piece of concurrent running code in the process, so thread requires the OPERATING system to invest CPU resources to run and schedule. The advantage of multithreading is obvious, the processing procedures in the thread is still sequential execution, in line with the habits of ordinary people’s thinking, so the programming is simple. However, the disadvantages of multithreading are also obvious, and the use (abuse) of threads can bring the additional burden of context switching to the system. And shared variables between threads can cause deadlocks
- Asynchronous operations require no additional thread load and are handled using callbacks. In a well-designed case, handlers can eliminate (if not eliminate, at least reduce the number of shared variables), reducing the possibility of deadlocks. Asynchronous operations are not flawless, of course. The complexity of writing asynchronous operations is relatively high, and the program mainly uses callback to process, which is somewhat different from ordinary people’s way of thinking and difficult to debug.
Here’s a question. Asynchronous operation did not create a new thread, we will think, such as a file operations, a large amount of data read from the hard disk, if use of the single thread synchronization nature will wait for a long time, but if use asynchronous operations, we let the data read asynchronous, two threads during data read to do other things, we will think, this how line? Asynchrony does not create another thread, a thread to do other things, so the data read asynchronous execution is done by who? In fact, the essence is this.
Friends who are familiar with computer hardware must be familiar with the word DMA, hard disk, cd-rom technical specifications have a clear DMA mode indicators, in fact, network card, sound card, graphics card is also DMA function. DMA stands for direct memory access, which means that dMA-enabled hardware can exchange data with memory without consuming CPU resources. As soon as the CPU sends a command to initiate a data transfer, the hardware begins exchanging data with memory itself, and when the transfer is complete, the hardware triggers an interrupt to notify that the operation is complete. These CPU-free I/O operations are the hardware basis for asynchronous operations. So even in a single-process (and thread-free) system like DOS, asynchronous DMA operations can also be initiated.
That is, the CPU only needs to do two things in the process of reading data for a long time. First, it issues instructions to start data exchange. Second, the switch ends, the instructions are received, and the CPU performs subsequent operations. In the middle, the CPU doesn’t have to participate in the long waiting process to read the data. Sequential execution means I don’t participate but I have to wait, which is inefficient; Asynchronous execution is, I don’t need to participate so I can go do something else, and you can call me when you’re done.
But what if there are some asynchronous operations that have to be done by the CPU, that is, the thread THAT I started can’t get out of here? NET has thread pools to do this. Thread pools can efficiently start a new thread to perform asynchronous operations. In Python, this is arranged by the system itself without human intervention, which is more efficient than creating many threads.
Conclusion:
- “Multithreading”, the first, the biggest problem lies in the thread itself scheduling and running need a lot of time, so it is not recommended to create too many threads; Second, the scheduling of shared resources is difficult, involving deadlocks, locking and other related concepts.
- The biggest problem with asynchrony is the “callback”, which makes software design more difficult.
In practical design, we can combine the two:
- When I/O operations need to be performed, using asynchronous operations is more appropriate than using thread + synchronous I/O operations. I/O operations not only include direct file and network read and write, but also include database operations, WebService, HttpRequest and. NetRemoting cross-process calls. Asynchrony is particularly suitable for most IO intensive applications.
- Threads, on the other hand, are suitable for applications that require long CPU time, such as long graphics processing and algorithm execution. However, threads are often used to perform long I/O operations due to the simplicity and conventions of using threaded programming. This is fine when there are only a few concurrent operations, but not when you need to handle a large number of concurrent operations.