Original:Callback Hell
What is "Callback Hell"?
In JavaScript, we often use callbacks to implement asynchronous logic, and once the nesting level is more, the code structure becomes very intuitive and ends up looking like this:
Fs.readdir (Source,function(err, files) {if(Err) {Console.log (' Error finding files: ' +err)} Else{Files.foreach (function(filename, fileindex) {console.log (filename) GM (source+ filename). Size (function(err, values) {if(Err) {Console.log (' Error identifying File size: ' +err)} Else{console.log (filename+ ': ' +values) Aspect= (Values.width/values.height) Widths.foreach (function(width, widthindex) {height= Math.Round (Width/aspect) Console.log (' Resizing ' + filename + ' to ' + height + ' x ' +height) This. Resize (width, height). write (dest + ' W ' + width + ' _ ' + filename,function(err) {if(err) Console.log (' Error writing file: ' +err)}) }.bind ( This)) } }) }) }})
Pyramid shape and end of a lot of }) , this is the callback Hell of Moe.
This is a mistake that many developers are prone to making, hoping to write JavaScript in a way that visually goes from top to bottom, creating a callback hell.
In some other programming languages (such as C, Ruby, Python), it is ensured that line 1th is completed and the file is loaded before the 2nd line of code is executed. But as you know, JavaScript is not.
What is a callback?
A callback (callbacks) is just a generic salutation to a function's use, and in JavaScript, there is no specific thing called a callback, it's just a good salutation.
Unlike functions that return results immediately, the callback function takes a certain amount of time to get the result.
Note: Depending on the description of callback on the wiki, the callback is divided into synchronous callbacks and asynchronous callbacks, which should be specifically asynchronous callbacks. For details see:Callback (computer programming).
This execution is immediate as in a synchronous callback, or it might happen at a later time as in an asy Nchronous callback.
"Asynchronous (Async)", also known as "async", means "it takes time" or "happens in the future, not now".
When I am working with I/O, callbacks are commonly used, such as downloading, reading files, interacting with the database, and so on.
When calling a normal function, we can use its return value directly:
var result = Multiplytwonumbers (5, ten) console.log (Result)// console print out
Asynchronous functions that use callbacks do not return results immediately:
var photo = Downloadphoto (' http://coolcats.com/cat.gif ')// photo undefined!
It may take a long time to download the GIF file, and you certainly do not want the program to be paused (that is, "block") state during the download process.
You can put the action that needs to be performed after the download is done in a function, which is the callback function. Pass it on to Downloadphoto , and when the download is complete, Downloadphoto executes the callback function (Callback,call you back later), and the error message or Photo (picture data) is passed to it.
Downloadphoto (' Http://coolcats.com/cat.gif ', Handlephoto)function handlephoto (Error, photo) { if (Error) console.error (' Download Error! ', error 'else console.log (' Download done ', photo)}console.log (' Start download ')
The biggest difficulty in understanding callbacks is figuring out the order in which code is executed when the program runs. In this example there are three key points: first declaring the handlephoto function, then invoking the Downloadphoto function and passing Handlephoto as a callback function to it, and finally "open Download "is printed out.
Note that at this point Handlephoto has not been invoked, just created and passed to Downloadphoto as a callback function, which will not be executed until Downloadphoto completes the task, depending on the speed How fast.
This example wants to convey two important concepts:
- The callback function Handlephoto is just a way to store operations that can be executed after a certain period of time (satisfying specific conditions).
- The order of code execution is not based on visual top-down, but on the logical completion-time jump trigger.
Translator Note: for the execution of asynchronous callbacks, you can refer to the JavaScript event loop .
How to deal with callback hell?
The callback hell stems from the lack of experience in development, and fortunately it is not difficult to write the code well. You only need to follow the following three principles:
1. Avoid function nesting
Here is a messy code that uses browser-request to launch an AJAX request to the server:
var form = document.queryselector (' form ' = function (submitevent) { var name = document.queryselector (' input ' "http://example.com/upload" "POST" }, function (err, response, body) { var StatusMessage = Document.queryselector ('. Status ' if (ERR) return statusmessage.value = err Statusmessage.value = Body})}
There are two anonymous functions in the code to give them a name!
varform = document.queryselector (' form ') Form.onsubmit=function formsubmit (submitevent) {varName = Document.queryselector (' input '). Value request ({uri:"Http://example.com/upload", Body:name, method:"POST" }, function postresponse (err, response, body) {varStatusMessage = Document.queryselector ('. Status ')) if(ERR)returnStatusmessage.value =Err Statusmessage.value=body})}
As you can see, naming a function is simple, but immediate:
- A function name with descriptive meaning that makes the code easier to read
- When an exception occurs, you can see an exact function name in the stack instead of "anonymous"
- It is easy to move functions and then reference them by function names
Now we can move these functions to the outermost layer of the program:
document.queryselector (' form '). onsubmit = formsubmit function Formsubmit (submitevent) { var name = document.queryselector (' input ' "http://example.com/upload" "POST" }, Postresponse)} function Postresponse (err, response, body) { var StatusMessage = doc Ument.queryselector ('. Status ' if (Err) Span style= "COLOR: #0000ff" >return statusmessage.value = err Statusmessage.value = body}
Note that the function declaration is moved to the bottom of the file, thanks to the function declaration promotion (Functionshoisting).
2, Modular
This is the most important point: everyone can engage in modules (i.e. code libraries).
Anyone is capable of creating modules (aka libraries)
Reference (node. JS project)ISAAC schlueter : "Write small modules with a single responsibility and assemble them for greater functionality." Callback Hell you won't fall in without touching it. ”
Write small modules that each does one thing, and assemble them into other modules so do a bigger thing. You can ' t get to callback Hell if you don ' t go there.
Let's extract the boilerplate code from the above code, split it into two files, and turn it into a module. I'll show you a module pattern, which can be used both in the browser and on the server side.
Create a new file called Formuploader.js , which contains two functions extracted from the above code:
module.exports . Submit = Formsubmit Span style= "COLOR: #0000ff" >function Formsubmit (submitevent) { var name = document.queryselector (' input ' "http://example.com/upload" "POST" }, Postresponse)} function Postresponse (err, response, body) { var StatusMessage = Document.queryselector ('. Status ' if (ERR) return statusmessage.value = err Statusmessage.value = body}
Module.exports is a use of the node. JS module System for node, Electron, and browser using browserify . I like this modular style very much, because it is wide, easy to understand, and does not require complex configuration files or scripts.
Now that we have formuploader.js (and as an external script for the page has been loaded), we just need to introduce (require) This module and use it!
The specific code of the program is as follows:
var require (' Formuploader ') document.queryselector (' form '). onsubmit = Formuploader.submit
The program only requires two lines of code and has the following benefits:
- Easier for new developers to understand-they don't have to be trapped in "forced to read through all formuploader functions"
- Formuploader can be used elsewhere without the need to copy code, and it's easier to share it with GitHub or NPM
3. Handling every Error
There are many types of errors: syntax errors (usually as long as you run a program), run-time errors (the program is working properly but there are some bugs that can cause logical confusion), platform errors (such as invalid file permissions, hardware driver invalidation, network connectivity anomalies, and so on). This section focuses on the last category of errors.
The first two principles will make your code more readable, and this principle will make your code more stable.
After the callback function is defined and assigned, it is executed in the background and then successfully completed or aborted. Any experienced developer will tell you that you can never predict when an error will occur, and you only assume it will happen.
The most popular way to handle errors in callback functions is the node. JS Style: The first parameter of a callback function is always "error".
var fs = require (' FS ') fs.readfile ('/does/not/exist 'function handlefile ( Error, file) { ifreturn console.error (' horizontal slot, error ', error) // OK, you can use ' file ' in your code.
Setting the first parameter to error is a simple convention that encourages you to remember to handle errors. If you set it to the second parameter, you might write the code as function handlefile (file) {} , ignoring error handling.
The Coding Specification Checker (code linters) can also be configured to help you remember to handle callback errors. Using the simplest one is standard , you only need to execute the $ standard command in the code directory, and it will show all the callback functions in the code that do not handle the error.
Points
(in construction ...) )
Read more
My callbacks are described in more detail.
Tutorials on the nodeschool
Browserify-handbook Example of writing module code
About PROMISES/GENERATORS/ES6
(in construction ...) )
Remember, only you can prevent callbacks to hell and forest fires
You can view the relevant source code on this GitHub .
Callback Hell--javascript Async Programming Guide