Home > Article > Web Front-end > The basic concepts of asynchronous and callback in JavaScript and the phenomenon of callback hell
This article brings you relevant knowledge about javascript. It mainly introduces the basic concepts of asynchronous and callbacks in JavaScript, as well as the phenomenon of callback hell. This article mainly introduces the basic concepts of asynchronous and callbacks. , the two are the core content of JavaScript. Let’s take a look at them together. I hope it will be helpful to everyone.
[Related recommendations: javascript video tutorial, web front-end】
Before studying the content of this article, we must first understand the concept of asynchronous. The first thing to emphasize is that there is an essential difference between asynchronous and parallel.
CPU
, or multiple CPU
, or multiple physical hosts or even multiple networks. CPU
temporarily put aside the current task, process the next task first, and then return to the previous task after receiving the callback notification of the previous task. The task continues to execute, No need for a second thread to participate in the entire process. Perhaps it is more intuitive to use pictures to explain parallelism, synchronization and asynchronousness. Assume that there are two tasks A and B that need to be processed. The parallel, synchronous and asynchronous processing methods will be as follows: The execution method shown:
JavaScript
provides us with many asynchronous functions Functions, these functions allow us to conveniently execute asynchronous tasks, that is, we start to execute a task (function) now, but the task will be completed later, and the specific completion time is not clear.
For example, the setTimeout
function is a very typical asynchronous function. In addition, fs.readFile
and fs.writeFile
are also asynchronous functions.
We can define an asynchronous task case ourselves, such as customizing a file copy function copyFile(from,to)
:
const fs = require('fs') function copyFile(from, to) { fs.readFile(from, (err, data) => { if (err) { console.log(err.message) return } fs.writeFile(to, data, (err) => { if (err) { console.log(err.message) return } console.log('Copy finished') }) }) }
FunctioncopyFile
First read the file data from the parameter from
, and then write the data to the file pointed to by the parameter to
.
We can call copyFile
like this:
copyFile('./from.txt','./to.txt')//复制文件
If at this time, there are other codes behind copyFile(...)
, then the program It does not wait for the execution of copyFile
to end, but executes it directly. The program does not care when the file copy task ends.
copyFile('./from.txt','./to.txt') //下面的代码不会等待上面的代码执行结束 ...
After executing this point, everything seems to be normal, but if we directly access the file ./to.txt after the
copyFile(...) function What happens to the content in
?
This will not read the copied content, just like this:
copyFile('./from.txt','./to.txt') fs.readFile('./to.txt',(err,data)=>{ ... })
If the ./to.txt
file has not been created before executing the program, it will Got the following error:
PS E:\Code\Node\demos\03-callback> node .\index.js
finished
Copy finished
PS E:\Code \Node\demos\03-callback> node .\index.js
Error: ENOENT: no such file or directory, open 'E:\Code\Node\demos\03-callback\to.txt'
Copy finished
Even if ./to.txt
exists, the copied content cannot be read.
The reason for this phenomenon is: copyFile(...)
is executed asynchronously. After the program executes the copyFile(...)
function, and It does not wait for the copy to be completed, but executes it directly, resulting in an error that the file ./to.txt
does not exist, or an error that the file content is empty (if the file is created in advance).
The specific execution end time of the asynchronous function cannot be determined, for example, the readFile(from,to)
function The execution end time most likely depends on the size of the file from
.
So, the question is how can we accurately locate the end of copyFile
execution and read the contents of the to
file?
This requires the use of a callback function. We can modify the copyFile
function as follows:
function copyFile(from, to, callback) { fs.readFile(from, (err, data) => { if (err) { console.log(err.message) return } fs.writeFile(to, data, (err) => { if (err) { console.log(err.message) return } console.log('Copy finished') callback()//当复制操作完成后调用回调函数 }) }) }
In this way, if we need to perform some operations immediately after the file copy is completed, just You can write these operations into the callback function:
function copyFile(from, to, callback) { fs.readFile(from, (err, data) => { if (err) { console.log(err.message) return } fs.writeFile(to, data, (err) => { if (err) { console.log(err.message) return } console.log('Copy finished') callback()//当复制操作完成后调用回调函数 }) }) } copyFile('./from.txt', './to.txt', function () { //传入一个回调函数,读取“to.txt”文件中的内容并输出 fs.readFile('./to.txt', (err, data) => { if (err) { console.log(err.message) return } console.log(data.toString()) }) })
If you have prepared the ./from.txt
file, then the above code can be run directly:
PS E:\Code\Node\demos\03-callback> node .\index.js
Copy finished
加入社区“仙宗”,和我一起修仙吧
社区地址:http://t.csdn.cn/EKf1h
这种编程方式被称为“基于回调”的异步编程风格,异步执行的函数应当提供一个回调参数用于在任务结束后调用。
这种风格在JavaScript
编程中普遍存在,例如文件读取函数fs.readFile
、fs.writeFile
都是异步函数。
回调函数可以准确的在异步工作完成后处理后继事宜,如果我们需要依次执行多个异步操作,就需要嵌套回调函数。
案例场景:依次读取文件A和文件B
代码实现:
fs.readFile('./A.txt', (err, data) => { if (err) { console.log(err.message) return } console.log('读取文件A:' + data.toString()) fs.readFile('./B.txt', (err, data) => { if (err) { console.log(err.message) return } console.log("读取文件B:" + data.toString()) }) })
执行效果:
PS E:\Code\Node\demos\03-callback> node .\index.js
读取文件A:仙宗无限好,只是缺了佬读取文件B:要想入仙宗,链接不能少
http://t.csdn.cn/H1faI
通过回调的方式,就可以在读取文件A之后,紧接着读取文件B。
如果我们还想在文件B之后,继续读取文件C呢?这就需要继续嵌套回调:
fs.readFile('./A.txt', (err, data) => {//第一次回调 if (err) { console.log(err.message) return } console.log('读取文件A:' + data.toString()) fs.readFile('./B.txt', (err, data) => {//第二次回调 if (err) { console.log(err.message) return } console.log("读取文件B:" + data.toString()) fs.readFile('./C.txt',(err,data)=>{//第三次回调 ... }) }) })
也就是说,如果我们想要依次执行多个异步操作,需要多层嵌套回调,这在层数较少时是行之有效的,但是当嵌套次数过多时,会出现一些问题。
回调的约定
实际上,fs.readFile
中的回调函数的样式并非个例,而是JavaScript
中的普遍约定。我们日后会自定义大量的回调函数,也需要遵守这种约定,形成良好的编码习惯。
约定是:
callback
的第一个参数是为 error 而保留的。一旦出现 error,callback(err)
就会被调用。callback(null, result1, result2,...)
就会被调用。基于以上约定,一个回调函数拥有错误处理和结果接收两个功能,例如fs.readFile('...',(err,data)=>{})
的回调函数就遵循了这种约定。
如果我们不深究的话,基于回调的异步方法处理似乎是相当完美的处理方式。问题在于,如果我们有一个接一个 的异步行为,那么代码就会变成这样:
fs.readFile('./a.txt',(err,data)=>{ if(err){ console.log(err.message) return } //读取结果操作 fs.readFile('./b.txt',(err,data)=>{ if(err){ console.log(err.message) return } //读取结果操作 fs.readFile('./c.txt',(err,data)=>{ if(err){ console.log(err.message) return } //读取结果操作 fs.readFile('./d.txt',(err,data)=>{ if(err){ console.log(err.message) return } ... }) }) }) })
以上代码的执行内容是:
随着调用的增加,代码嵌套层级越来越深,包含越来越多的条件语句,从而形成不断向右缩进的混乱代码,难以阅读和维护。
我们称这种不断向右增长(向右缩进)的现象为“回调地狱”或者“末日金字塔”!
fs.readFile('a.txt',(err,data)=>{ fs.readFile('b.txt',(err,data)=>{ fs.readFile('c.txt',(err,data)=>{ fs.readFile('d.txt',(err,data)=>{ fs.readFile('e.txt',(err,data)=>{ fs.readFile('f.txt',(err,data)=>{ fs.readFile('g.txt',(err,data)=>{ fs.readFile('h.txt',(err,data)=>{ ... /* 通往地狱的大门 ===> */ }) }) }) }) }) }) }) })
虽然以上代码看起来相当规整,但是这只是用于举例的理想场面,通常业务逻辑中会有大量的条件语句、数据处理操作等代码,从而打乱当前美好的秩序,让代码变的难以维护。
幸运的是,JavaScript
为我们提供了多种解决途径,Promise
就是其中的最优解。
【相关推荐:javascript视频教程、web前端】
The above is the detailed content of The basic concepts of asynchronous and callback in JavaScript and the phenomenon of callback hell. For more information, please follow other related articles on the PHP Chinese website!