This is the 20th day of my participation in the August More Text Challenge
📣 Hello everyone, I am Xiao Cheng, this article will show you how to understand and feel asynchronous programming in JavaScript
The introduction
Let’s start with a very common scenario: operating on data returned from the server
The interaction with the server side is an asynchronous operation
If you were writing normal code, you might write something like this
I don’t know what I’m typing, but it’s basically an asynchronous request that returns an assignment to data and says,
let data = ajax("http://127.0.0.1",ab) // Just write it
console.log(data)
Copy the code
Although the whole idea doesn’t seem wrong, right? However, it does not work. The data retrieval is asynchronous, which means that when the data is requested, the output has already been executed, which must be undefined
So why did it do it?
JavaScript is a single-threaded language. What would happen if asynchronous execution were eliminated
It’s like shopping. You have to follow the person in front of you. He stops to buy something and everyone behind him stops and waits for him to come back. Switching to JS is the same, blocking code execution. Hence the concept of asynchrony. Let’s first look at the concept of asynchrony and how asynchronous operations are traditionally implemented
What are synchronous and asynchronous
Synchronization: Tasks are executed sequentially. When a large number of time-consuming tasks are encountered, subsequent tasks will be delayed. This delay is called blocking
Asynchronous: It does not wait for time-consuming tasks, but immediately executes the next task after encountering asynchronous tasks. The subsequent logic of time-consuming tasks is usually defined by callback functions, and the code execution sequence is out of order
Implementing asynchronous programming
Prior to ES6, there were several approaches to asynchronous programming.
- The callback function
- Event listeners
- Publish/subscribe
- Promise object
Let’s start by reviewing how asynchronous programming is done in the traditional way
Callback
A callback function can be understood as a desired thing, defined by the caller, given to the executor at a certain time, put the required operation in the function, and pass the function to the executor to execute
This is mainly reflected in writing the second paragraph of the task in a function, which is called directly when the task is re-executed
Then one might ask, what is the second paragraph? Let’s take another example, reading a file and printing it. It must be asynchronous, but how can it be divided into two paragraphs?
According to the logical division, the first paragraph is to read the file, the second paragraph is to print the file, which can be understood as the first paragraph is to request data, the second paragraph is to print data
Ruan teacher’s code example
fs.readFile('/etc/passwd'.'utf-8'.function (err, data) {
if (err) throw err;
console.log(data);
});
Copy the code
At the end of the first phase, the result is returned to the following function as an argument, passing in the second segment
Callbacks are used in the following scenarios:
-
Event callback
-
Timer callback
-
An Ajax request
Promise
There is no problem with the callbacks themselves, but the problem arises in the nesting of multiple callbacks
Think about it, I execute you, you execute him, he execute her…
Is it necessary to layer upon layer of nesting, that nesting doll operation is obviously not conducive to reading
fs.readFile(fileA, 'utf-8'.function (err, data) {
fs.readFile(fileB, 'utf-8'.function (err, data) {
// ...
});
});
Copy the code
You can also think of it this way: if one of the codes needs to be changed, both its upper and lower callbacks need to be changed, which is also called strong coupling
Coupling, lotus root, correlation is very strong meaning
This scenario is also called “callback hell.”
The Promise object was created to solve this problem, using a new way of writing: chain calls
Promise can be used to indicate the execution state of an asynchronous task, and there are three states
- Pending: Indicates that the start state is waiting
- This is a big pity: the state of success will trigger onFulfilled
- Rejected: Indicates the failed state. OnRejected is triggered
It is written as follows
const promise = new Promise(function(resolve, reject) {
// Synchronize the code
// resolve Execution indicates that the asynchronous task succeeds
// reject Execution Indicates that asynchronous tasks fail
resolve(100)
// reject(new Error('reject')) // fail
})
promise.then(function() {
// Successful callback
}, function () {
// Failed callback
})
Copy the code
The Promise object calls the THEN method and returns a new Promise object, which can then continue the chain call
The subsequent THEN method registers the callback for the Promise object returned by the previous THEN
The return value of the callback function from the previous THEN method is used as an argument to the callback from the subsequent THEN method
The purpose of chain calls is to solve the problem of nested callback functions
More details on Promise won’t be covered here, but will be covered in the next article
Broken, broken, nested loops, I’m in callback hell, try to be more literal
Promise solved callback hell, and it wasn’t the ultimate solution to asynchronous programming, so what problems did it cause?
- Unable to cancel Promise
- When in a pending state, no progress is known
- Errors cannot be
catch
But none of this is the biggest problem with Promise. Its biggest problem is code redundancy. When the execution logic becomes complicated, the semantics of the code become very unclear, and it’s all then
For those of you who have read the previous article, this should give you an idea of how Generator can implement asynchronous programming. It seems that the next method can start, run, and pass parameters as well as the then method
Generator
The Generator function can pause and resume execution, which is the fundamental reason it encapsulates asynchronous tasks. In addition, it has two characteristics that make it a perfect solution for asynchronous programming.
- Data transfer in and out of a function
- Error handling mechanism
The data transfer
Before we look at how asynchronous programming is implemented, let’s review how Generator functions are executed
// Declare a Generator function
function* gen(x){
let y = yield x + 2
return y
}
// Traversal object
let g = gen()
// Call the next method the first time
g.next() // { value: 3, done: false }
// The second call passes arguments
g.next(2) // { value: 2, done: true }
Copy the code
The gen function is first executed to obtain the traverser object, which is not executed. When the next method of the traverser object is called, the first yield statement is executed, and so on
That is, only if the next method is called will the execution proceed
Meanwhile, in the above code, we can get the returned value by value and exchange data by passing parameters to the next method
Error handling mechanism
Error handling code can be deployed inside the Generator functions to catch errors thrown outside the function
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('Wrong');
Copy the code
Maybe some people don’t understand why an internal catch can catch an external error?
The reason is that we throw the error through g.row, which is actually throwing the error into the generator, since we are calling the throw method on p after all
Implementing asynchronous programming
In my last article, I covered the execution mechanism of generators in detail, along with the yield execution characteristics, which you can read first
The idea is to use generator functions to implement asynchronous programming by using yield to suspend the execution of generator functions. Let’s look at an example
Generator + Promise
function * main () {
const user = yield ajax('/api/usrs.json')
console.log(user)
}
const g = main()
const result = g.next()
result.value.then(data= > {
g.next(data)
})
Copy the code
First we define a generator function main, and then use yield inside that function to return an Ajax call, which returns a Promise object.
It then receives the return value of the yield statement, which is the argument to the second next method.
We can call the generator function from the outside to get its iterator object, and then call the next method on that object, so that the main function will execute to the first yield, which is the ajax call, In this case, the value of the object returned by the next method is the Promise object returned by Ajax
So we can specify the Promise callback with the then method, and in the Promise callback we can get the Promise execution result data, and then we can call the next method again, We pass our data so that main can continue, and data is assigned to the user as the yield expression return value
Asynchronous iterator
If generator + Promise above is anything to go by, this is simply asynchronous programming using a generator
function foo(x, y) {
ajax("1.2.34.2".function(err,data) {
if(err) {
it.throw(err)
}else {
it.next(data)
}
})
}
function *main() {
let text = yield foo(11.31)
console.log( text )
}
const it = main()
it.next()
Copy the code
This is a simple example in the code above, and while it may seem like a lot more than the callback function implementation, you can see that the code logic is much better
The most critical piece of code in this
let text = yield foo(11.31)
console.log( text )
Copy the code
We explained this in the last part
In yield Foo (11, 31), foo(11, 31) is first called with no return value, a request is sent for data, the request succeeds, and it.next(data) is called, thus making data the return value of the previous yield, thus synchronizing the asynchronous code
async await
There is a lot more to Generator, tools, concurrency, delegates, and so on, which make generators very powerful, but also makes writing an executor function more cumbersome, so in ES7 we added async await keyword pairs, which are much easier to use.
Async functions are syntactic sugar for generator functions.
It is syntactically very similar to Generator functions, simply change Generator functions to async keyword modified functions and change yield to await. This function can be called directly from the outside and executed in exactly the same way as the Generator function
Compared with Generator async, the biggest advantage is that it does not need to be used with some tools, such as Co and Runner
The reason is that it is standard asynchronous programming at the language level, and async functions can return a Promise object, which also helps control the code.
Note that await can only appear in the async function body
// Change generator functions to async modified functions
async function main() {
try {
// change yield to await
const a = await ajax('xxxx')
console.log(a)
const b = await ajax('xxx')
console.log(b)
const c = await ajax('xx')
console.log(c)
} catch (e) {
console.log(e)
}
}
// Return a Promise object
const promise = main()
Copy the code
As we can see from the code above, we do not need to control execution through next as Generator does
Async await is a combination of Generator and Promise that solves the problem left by the previous method and should be the best solution for handling asynchrony at the moment
conclusion
This article describes the four stages of asynchronous programming as a progressive process that addresses the problems of the previous approach step by step.
- The callback function: causes two problems
- Lack of orderliness: Callback hell, resulting in code that is difficult to maintain, poorly read, and so on
- Lack of trusted capricious: Inversion of control, resulting in code that may execute incorrectly
- Promise: Solved the problem of trusted caprices, but the code was too redundant
- Generator: Solves the sequential problem but requires manual control
next
And the code used with the tool can be very complex - Async await: to combine
generator + promise
, no manual call, perfect solution
reference
- JavaScript asynchronous programming
- Asynchronous application of Generator functions
- JavaScript Advanced Programming (4th Edition)
That’s all for this article, hope you like 💛, any questions can be left in the comments section oh ~