Asynchronous is to increase the CPU occupancy rate and keep it busy all the time.
Some operations (the most typical one is I/O) do not require CPU participation and are very time-consuming. If asynchronous is not used, it will form a blocking state, causing the CPU to idle and the page to freeze.
When an I/O operation occurs in an asynchronous environment, the CPU puts the I/O work aside (at this time, the I/O is taken over by other controllers and data is still being transmitted), and then processes the next task, waiting for the I/O operation to be completed. Notify the CPU (callback is a notification method) to come back to work.
The core content of "JavaScript Asynchronous and Callback" is that the specific end time of asynchronous work is uncertain. In order to accurately perform subsequent processing after the asynchronous work is completed, a callback needs to be passed into the asynchronous function, so that After completing your work continue with the following tasks.
Although callbacks can be very simple to implement asynchronously, they can form callback hell due to multiple nesting. To avoid callback hell, you need to denest and change nested programming to linear programming.
Promise
is the best solution to handle callback hell in JavaScript
.
Promise
can be translated as "promise". We can encapsulate asynchronous work and call it a Promise
, that is, make a promise and promise to give a clear signal after the asynchronous work ends!
Promise
syntax:
let promise = new Promise(function(resolve,reject){ // Asynchronous work})
Through the above syntax, we can encapsulate asynchronous work into a Promise
. The function passed in when creating Promise
is the method for handling asynchronous work, also known as executor
executor.
resolve
and reject
are callback functions provided by JavaScript
itself. They can be called when executor
completes the task:
resolve(result)
- if it is completed successfully, result
will be returned;reject(error)
- if the execution fails and error
will be generated;executor
will automatically execute immediately after Promise
is created, and its execution status will change the state of the internal properties of Promise
:
state
- initially pending
, then converted to fulfilled
after resolve
is called, or becomes rejected
when reject
is called;result
——It undefined
initially, and then becomes value
after resolve(value)
is called, or becomes error
after reject
is called;fs.readFile
an asynchronous function. We can pass it in executor
File reading operations are performed in the file, thereby encapsulating asynchronous work.
The following code encapsulates the fs.readFile
function, and uses resolve(data)
to handle successful results and reject(err)
to handle failed results.
The code is as follows:
let promise = new Promise((resolve, reject) => { fs.readFile('1.txt', (err, data) => { console.log('Read 1.txt') if (err) reject(err) resolve(data) })})
If we execute this code, the words "Read 1.txt" will be output, proving that the file reading operation is performed immediately after Promise
is created.
Promise
usually encapsulates asynchronous code internally, but it does not only encapsulate asynchronous code.
The above Promise
case encapsulates the file reading operation. The file will be read immediately after the creation is completed. If you want to get the result of Promise
execution, you need to use three methods then
, catch
and finally
.
The then
method of Promise
can be used to handle the work after Promise
execution is completed. It receives two callback parameters. The syntax is as follows:
promise.then(function(result),function(error))
result
is the value received by resolve
;error
is the parameter received by reject
;for example:
let promise = new Promise((resolve, reject) => { fs.readFile('1.txt', (err, data) => { console.log('Read 1.txt') if (err) reject(err) resolve(data) })})promise.then( (data) => { console.log('Successfully executed, the result is' + data.toString()) }, (err) => { console.log('Execution failed, error is' + err.message) })
If the file reading is executed successfully, the first function will be called:
PS E:CodeNodedemos 3-callback> node .index.js Read 1.txt If executed successfully, the result is 1
deleted 1.txt
. If the execution fails, the second function will be called:
PS E:CodeNodedemos 3-callback> node .index.js Read 1.txt The execution failed with the error ENOENT: no such file or directory, open 'E:CodeNodedemos 3-callback1.txt'
If we only focus on the result of successful execution, we can pass in only one callback function:
promise .then((data)=>{ console.log('Successfully executed, the result is' + data.toString())})
At this point we have implemented an asynchronous reading operation of the file.
If we only focus on the failure result, we can pass null
to the first then
callback: promise.then(null,(err)=>{...})
.
Or use a more elegant way: promise.catch((err)=>{...})
let promise = new Promise((resolve, reject) => { fs.readFile('1.txt', (err, data) => { console.log('Read 1.txt') if (err) reject(err) resolve(data) })})promise.catch((err)=>{ console.log(err.message)})
.catch((err)=>{...})
and then(null,(err)=>{...})
have exactly the same effect.
.finally
is a function that will be executed regardless of the result of promise
. It has the same purpose as finally
in try...catch...
syntax, and can handle operations unrelated to the result.
For example:
new Promise((resolve,reject)=>{ //something...}).finally(()=>{console.log('Execute regardless of the result')}).then(result=>{...}, err=>{...} )The finally callback has no parameters,
fs.readFile()
finally
finally
the result of promise
will be passed, so it can still be done after finally
.then
fs.readFile()
method reads 10 files sequentially and outputs the contents of the ten files sequentially.
Since fs.readFile()
itself is asynchronous, we must use callback nesting. The code is as follows:
fs.readFile('1.txt', (err, data) => { console.log(data.toString()) //1 fs.readFile('2.txt', (err, data) => { console.log(data.toString()) fs.readFile('3.txt', (err, data) => { console.log(data.toString()) fs.readFile('4.txt', (err, data) => { console.log(data.toString()) fs.readFile('5.txt', (err, data) => { console.log(data.toString()) fs.readFile('6.txt', (err, data) => { console.log(data.toString()) fs.readFile('7.txt', (err, data) => { console.log(data.toString()) fs.readFile('8.txt', (err, data) => { console.log(data.toString()) fs.readFile('9.txt', (err, data) => { console.log(data.toString()) fs.readFile('10.txt', (err, data) => { console.log(data.toString()) // ==> Hell's Gate}) }) }) }) }) }) }) }) })})
Although the above code can complete the task, as the call nesting increases, the code level becomes deeper and the maintenance difficulty increases, especially when we are using real code that may contain many loops and conditional statements. , instead of the simple console.log(...)
in the example.
If we do not use callbacks and directly call fs.readFile()
in sequence according to the following code, what will happen?
//Note: This is the wrong way to write fs.readFile('1.txt', (err, data) => { console.log(data.toString())})fs.readFile('2.txt', (err, data) => { console.log(data.toString())})fs.readFile('3.txt', (err, data) => { console.log(data.toString())})fs.readFile('4.txt', (err, data) => { console.log(data.toString())})fs.readFile('5.txt', (err, data) => { console.log(data.toString())})fs.readFile('6.txt', (err, data) => { console.log(data.toString())})fs.readFile('7.txt', (err, data) => { console.log(data.toString())})fs.readFile('8.txt', (err, data) => { console.log(data.toString())})fs.readFile('9.txt', (err, data) => { console.log(data.toString())})fs.readFile('10.txt', (err, data) => { console.log(data.toString())})
The following are the results of my test (the results of each execution are different):
PS E:CodeNodedemos 3-callback> node .index.The reason
js12346957108
produces this non-sequential result is asynchronous , not multi-threaded parallelism. Asynchronous can be achieved in a single thread.
The reason why this error case is used here is to emphasize the concept of asynchronousness. If you don’t understand why this result occurs, you must go back and make up for the lesson!
The idea of using Promise
to solve asynchronous sequential file reading:
promise1
, and use resolve
to return the result.promise1.then
to receive and output the file reading result.promise2
object in promise1.then
. And returnpromise2.then
to receive and output the reading resultpromise3
object in promise2.then
, and return topromise3.then
to receive and output the reading resultThe code is as follows:
let promise1 = new Promise( (resolve, reject) => { fs.readFile('1.txt', (err, data) => { if (err) reject(err) resolve(data) })})let promise2 = promise1.then( data => { console.log(data.toString()) return new Promise((resolve, reject) => { fs.readFile('2.txt', (err, data) => { if (err) reject(err) resolve(data) }) }) })let promise3 = promise2.then( data => { console.log(data.toString()) return new Promise((resolve, reject) => { fs.readFile('3.txt', (err, data) => { if (err) reject(err) resolve(data) }) }) })let promise4 = promise3.then( data => { console.log(data.toString()) //..... })... ...
In this way we write the original nested callback hell into a linear mode.
But there is still a problem with the code. Although the code has become more beautiful in terms of management, it greatly increases the length of the code.
The above code is too lengthy. We can reduce the amount of code through two steps:
promise
, and link the .then
code as follows:
function myReadFile (path) { return new Promise((resolve, reject) => { fs.readFile(path, (err, data) => { if (err) reject(err) console.log(data.toString()) resolve() }) })}myReadFile('1.txt') .then(data => { return myReadFile('2.txt') }) .then(data => { return myReadFile('3.txt') }) .then(data => { return myReadFile('4.txt') }) .then(data => { return myReadFile('5.txt') }) .then(data => { return myReadFile('6.txt') }) .then(data => { return myReadFile('7.txt') }) .then(data => { return myReadFile('8.txt') }) .then(data => { return myReadFile('9.txt') }) .then(data => { return myReadFile('10.txt') })
Since the myReadFile
method will return a new Promise
, we can directly execute the .then
method. This programming method is called chain programming .
The code execution result is as follows:
PS E:CodeNodedemos 3-callback> node .index.js12345678910
This completes the asynchronous and sequential file reading operation.
Note: A new
Promise
object must be returned in the.then
method of each step, otherwise the previous oldPromise
will be received.This is because each
then
method will continue to pass itsPromise
downward.