callback in js

来源:互联网 发布:131458淘宝信誉查询网 编辑:程序博客网 时间:2024/06/08 08:35

Understand JavaScript Callback Functions and Use Them

march. 4 2013  176

(Learn JavaScript Higher-order Functions, aka Callback Functions)

In JavaScript, functions are first-class objects; that is, functions are of the type Object and they can be used in a first-class manner like any other object (String, Array, Number, etc.) since they are in fact objects themselves. They can be “stored in variables, passed as arguments to functions, created within functions, and returned from functions”1.

Our Career Paths and Courses Website Is Now Live

Learn.Modern Developer Launched

Our first cohort is in session: 97% of our first cohort on target to graduate. Enroll in the second cohort. Career Path 1: JavaScript Developer and Career Path 3: Modern Frontend Developer usually fill up quickly.

https://learn.moderndeveloper.com

Table of Contents

  •  Our Career Paths and Courses Website Is Now Live
  •  Receive Updates
  •  What is a Callback or Higher-order Function?
  •  How Callback Functions Work?
  •  Basic Principles when Implementing Callback Functions
  •  “Callback Hell” Problem And Solution
  •  Make Your Own Callback Functions
  •  Final Words
  • Receive Updates

Because functions are first-class objects, we can pass a function as an argument in another function and later execute that passed-in function or even return it to be executed later. This is the essence of using callback functions in JavaScript. In the rest of this article we will learn everything about JavaScript callback functions. Callback functions are probably the most widely used functional programming technique in JavaScript, and you can find them in just about every piece of JavaScript and jQuery code, yet they remain mysterious to many JavaScript developers. The mystery will be no more, by the time you finish reading this article.

Callback functions are derived from a programming paradigm known as functional programming. At a fundamental level, functional programming specifies the use of functions as arguments. Functional programming was—and still is, though to a much lesser extent today—seen as an esoteric technique of specially trained, master programmers. 

Fortunately, the techniques of functional programming have been elucidated so that mere mortals like you and me can understand and use them with ease. One of the chief techniques in functional programming happens to be callback functions. As you will read shortly, implementing callback functions is as easy as passing regular variables as arguments. This technique is so simple that I wonder why it is mostly covered in advanced JavaScript topics.

What is a Callback or Higher-order Function?

A callback function, also known as a higher-order function, is a function that is passed to another function (let’s call this other function “otherFunction”) as a parameter, and the callback function is called (or executed) inside the otherFunction. A callback function is essentially a pattern (an established solution to a common problem), and therefore, the use of a callback function is also known as a callback pattern.

Consider this common use of a callback function in jQuery:

 //Note that the item in the click method's parameter is a function, not a variable.​ ​//The item is a callback function $("#btn_1").click(function() { alert("Btn 1 Clicked"); });

As you see in the preceding example, we pass a function as a parameter to the click method. And the click method will call (or execute) the callback function we passed to it. This example illustrates a typical use of callback functions in JavaScript, and one widely used in jQuery. 

Ruminate on this other classic example of callback functions in basic JavaScript:

 var friends = ["Mike", "Stacy", "Andy", "Rick"]; ​ friends.forEach(function (eachName, index){ console.log(index + 1 + ". " + eachName); // 1. Mike, 2. Stacy, 3. Andy, 4. Rick​ });

Again, note the way we pass an anonymous function (a function without a name) to the forEach method as a parameter.

So far we have passed anonymous functions as a parameter to other functions or methods. Lets now understand how callbacks work before we look at more concrete examples and start making our own callback functions.

How Callback Functions Work?

We can pass functions around like variables and return them in functions and use them in other functions. When we pass a callback function as an argument to another function, we are only passing the function definition. We are not executing the function in the parameter. In other words, we aren’t passing the function with the trailing pair of executing parenthesis () like we do when we are executing a function.

And since the containing function has the callback function in its parameter as a function definition, it can execute the callback anytime.

Note that the callback function is not executed immediately. It is “called back” (hence the name) at some specified point inside the containing function’s body. So, even though the first jQuery example looked like this:

 //The anonymous function is not being executed there in the parameter. ​ ​//The item is a callback function $("#btn_1").click(function() { alert("Btn 1 Clicked"); });

the anonymous function will be called later inside the function body. Even without a name, it can still be accessed later via the arguments object by the containing function. 

Callback Functions Are Closures
When we pass a callback function as an argument to another function, the callback is executed at some point inside the containing function’s body just as if the callback were defined in the containing function. This means the callback is a closure. Read my post, Understand JavaScript Closures With Ease for more on closures. As we know, closures have access to the containing function’s scope, so the callback function can access the containing functions’ variables, and even the variables from the global scope.

Basic Principles when Implementing Callback Functions

While uncomplicated, callback functions have a few noteworthy principles we should be familiar with when implementing them.

Use Named OR Anonymous Functions as Callbacks
In the earlier jQuery and forEach examples, we used anonymous functions that were defined in the parameter of the containing function. That is one of the common patterns for using callback functions. Another popular pattern is to declare a named function and pass the name of that function to the parameter. Consider this:

 // global variable​ ​var allUserData = []; ​ ​// generic logStuff function that prints to console​ ​function logStuff (userData) { if ( typeof userData === "string") { console.log(userData); } else if ( typeof userData === "object") { for (var item in userData) { console.log(item + ": " + userData[item]); } ​ } ​ } ​ ​// A function that takes two parameters, the last one a callback function​ ​function getInput (options, callback) { allUserData.push (options); callback (options); ​ } ​ ​// When we call the getInput function, we pass logStuff as a parameter.​ ​// So logStuff will be the function that will called back (or executed) inside the getInput function​ getInput ({name:"Rich", speciality:"JavaScript"}, logStuff); ​// name: Rich​ ​// speciality: JavaScript

Pass Parameters to Callback Functions
Since the callback function is just a normal function when it is executed, we can pass parameters to it. We can pass any of the containing function’s properties (or global properties) as parameters to the callback function. In the preceding example, we pass options as a parameter to the callback function. Let’s pass a global variable and a local variable:

 //Global variable​ ​var generalLastName = "Clinton"; ​ ​function getInput (options, callback) { allUserData.push (options); ​// Pass the global variable generalLastName to the callback function​ callback (generalLastName, options); }

Make Sure Callback is a Function Before Executing It
It is always wise to check that the callback function passed in the parameter is indeed a function before calling it. Also, it is good practice to make the callback function optional.

Let’s refactor the getInput function from the previous example to ensure these checks are in place. 

 function getInput(options, callback) { allUserData.push(options); ​ // Make sure the callback is a function​ if (typeof callback === "function") { // Call it, since we have confirmed it is callable​ callback(options); } }

Without the check in place, if the getInput function is called either without the callback function as a parameter or in place of a function a non-function is passed, our code will result in a runtime error.

Problem When Using Methods With The this Object as Callbacks
When the callback function is a method that uses the this object, we have to modify how we execute the callback function to preserve the this object context. Or else the this object will either point to the global window object (in the browser), if callback was passed to a global function. Or it will point to the object of the containing method.
Let’s explore this in code:


 // Define an object with some properties and a method​ ​// We will later pass the method as a callback function to another function​ ​var clientData = { id: 094545, fullName: "Not Set"// setUserName is a method on the clientData object​ setUserName: function (firstName, lastName) { // this refers to the fullName property in this object​ this.fullName = firstName + " " + lastName; } } ​ ​function getUserInput(firstName, lastName, callback) { // Do other stuff to validate firstName/lastName here​ ​ // Now save the names​ callback (firstName, lastName); }

In the following code example, when clientData.setUserName is executed, this.fullName will not set the fullName property on the clientData object. Instead, it will set fullName on the window object, since getUserInput is a global function. This happens because the this object in the global function points to the window object.

 getUserInput ("Barack", "Obama", clientData.setUserName); ​ console.log (clientData.fullName);// Not Set​ ​ ​// The fullName property was initialized on the window object​ console.log (window.fullName); // Barack Obama

Use the Call or Apply Function To Preserve this
We can fix the preceding problem by using the Call or Apply function (we will discuss these in a full blog post later). For now, know that every function in JavaScript has two methods: Call and Apply. And these methods are used to set the this object inside the function and to pass arguments to the functions. 

Call takes the value to be used as the this object inside the function as the first parameter, and the remaining arguments to be passed to the function are passed individually (separated by commas of course). The Apply function’s first parameter is also the value to be used as the thisobject inside the function, while the last parameter is an array of values (or the arguments object) to pass to the function. 

This sounds complex, but lets see how easy it is to use Apply or Call. To fix the problem in the previous example, we will use the Apply function thus:

 //Note that we have added an extra parameter for the callback object, called "callbackObj"​ ​function getUserInput(firstName, lastName, callback, callbackObj) { // Do other stuff to validate name here​ ​ // The use of the Apply function below will set the this object to be callbackObj​ callback.apply (callbackObj, [firstName, lastName]); } ​

With the Apply function setting the this object correctly, we can now correctly execute the callback and have it set the fullName property correctly on the clientData object:

 // We pass the clientData.setUserName method and the clientData object as parameters. The clientData object will be used by the Apply function to set the this object​ getUserInput ("Barack", "Obama", clientData.setUserName, clientData); ​ ​// the fullName property on the clientData was correctly set​ console.log (clientData.fullName); // Barack Obama

We would have also used the Call function, but in this case we used the Apply function.

Multiple Callback Functions Allowed
We can pass more than one callback functions into the parameter of a function, just like we can pass more than one variable. Here is a classic example with jQuery’s AJAX function:

 function successCallback() { // Do stuff before send​ } ​ ​function successCallback() { // Do stuff if success message received​ } ​ ​function completeCallback() { // Do stuff upon completion​ } ​ ​function errorCallback() { // Do stuff if error received​ } ​ $.ajax({ url:"http://fiddle.jshell.net/favicon.png", success:successCallback, complete:completeCallback, error:errorCallback ​ });

“Callback Hell” Problem And Solution

In asynchronous code execution, which is simply execution of code in any order, sometimes it is common to have numerous levels of callback functions to the extent that you have code that looks like the following. The messy code below is called callback hell because of the difficulty of following the code due to the many callbacks. I took this example from the node-mongodb-native, a MongoDB driver for Node.js. [2]. The example code below is just for demonstration:

 var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory}); p_client.open(function(err, p_client) { p_client.dropDatabase(function(err, done) { p_client.createCollection('test_custom_key', function(err, collection) { collection.insert({'a':1}, function(err, docs) { collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) { cursor.toArray(function(err, items) { test.assertEquals(1, items.length); ​ // Let's close the db​ p_client.close(); }); }); }); }); }); });

You are not likely to encounter this problem often in your code, but when you do—and you will from time to time—here are two solutions to this problem. [3]

  1. Name your functions and declare them and pass just the name of the function as the callback, instead of defining an anonymous function in the parameter of the main function.
  2. Modularity: Separate your code into modules, so you can export a section of code that does a particular job. Then you can import that module into your larger application.

    



Make Your Own Callback Functions

Now that you completely (I think you do; if not it is a quick reread :)) understand everything about JavaScript callback functions and you have seen that using callback functions are rather simple yet powerful, you should look at your own code for opportunities to use callback functions, for they will allow you to:

  • Do not repeat code (DRY—Do Not Repeat Yourself)
  • Implement better abstraction where you can have more generic functions that are versatile (can handle all sorts of functionalities)
  • Have better maintainability
  • Have more readable code
  • Have more specialized functions.

It is rather easy to make your own callback functions. In the following example, I could have created one function to do all the work: retrieve the user data, create a generic poem with the data, and greet the user. This would have been a messy function with much if/else statements and, even still, it would have been very limited and incapable of carrying out other functionalities the application might need with the user data. 

Instead, I left the implementation for added functionality up to the callback functions, so that the main function that retrieves the user data can perform virtually any task with the user data by simply passing the user’s full name and gender as parameters to the callback function and then executing the callback function.

In short, the getUserInput function is versatile: it can execute all sorts of callback functions with myriad of functionalities.

 // First, setup the generic poem creator function; it will be the callback function in the getUserInput function below.​ ​function genericPoemMaker(name, gender) { console.log(name + " is finer than fine wine."); console.log("Altruistic and noble for the modern time."); console.log("Always admirably adorned with the latest style."); console.log("A " + gender + " of unfortunate tragedies who still manages a perpetual smile"); } ​ ​//The callback, which is the last item in the parameter, will be our genericPoemMaker function we defined above.​ ​function getUserInput(firstName, lastName, gender, callback) { var fullName = firstName + " " + lastName; ​ // Make sure the callback is a function​ if (typeof callback === "function") { // Execute the callback function and pass the parameters to it​ callback(fullName, gender); } }

Call the getUserInput function and pass the genericPoemMaker function as a callback:

 getUserInput("Michael", "Fassbender", "Man", genericPoemMaker); ​// Output​ ​/* Michael Fassbender is finer than fine wine. Altruistic and noble for the modern time. Always admirably adorned with the latest style. A Man of unfortunate tragedies who still manages a perpetual smile. */

Because the getUserInput function is only handling the retrieving of data, we can pass any callback to it. For example, we can pass a greetUser function like this:

 function greetUser(customerName, sex) { var salutation = sex && sex === "Man" ? "Mr." : "Ms."; console.log("Hello, " + salutation + " " + customerName); } ​ ​// Pass the greetUser function as a callback to getUserInput​ ​getUserInput("Bill", "Gates", "Man", greetUser); ​ ​// And this is the output​ Hello, Mr. Bill Gates

We called the same getUserInput function as we did before, but this time it performed a completely different task. 

As you see, callback functions afford much versatility. And even though the preceding example is relatively simple, imagine how much work you can save yourself and how well abstracted your code will be if you start using callback functions. Go for it. Do it in the monings; do it in the evenings; do it when you are down; do it when you are k 

Note the following ways we frequently use callback functions in JavaScript, especially in modern web application development, in libraries, and in frameworks:


  • For asynchronous execution (such as reading files, and making HTTP requests)
  • In Event Listeners/Handlers
  • In setTimeout and setInterval methods
  • For Generalization: code conciseness

Final Words

JavaScript callback functions are wonderful and powerful to use and they provide great benefits to your web applications and code. You should use them when the need arises; look for ways to refactor your code for Abstraction, Maintainability, and Readability with callback functions.

See you next time, and remember to keep coming back because JavaScriptIsSexy.com has much to teach you and you have much to learn.

Notes

  1. http://c2.com/cgi/wiki?FirstClass
  2. https://github.com/mongodb/node-mongodb-native
  3. http://callbackhell.com/
  4. JavaScript Patterns by Stoyan Stefanov (Sep 28, 2010)


How to Write a Callback Function

If you’re writing your own functions or methods, then you might come across a need for a callback function. Here’s a very simple example of a custom callback function:

<span class="hljs-function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span> <span class="hljs-title" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(239, 239, 143); background-position: 0px 0px;">mySandwich</span><span class="hljs-params" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">(param1, param2, callback)</span> </span>{    alert(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'Started eating my sandwich.\n\nIt has: '</span> + param1 + <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">', '</span> + param2);    callback();}mySandwich(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'ham'</span>, <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'cheese'</span>, <span class="hljs-function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span><span class="hljs-params" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">()</span> </span>{    alert(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'Finished eating my sandwich.'</span>);});

Here we have a function called mySandwich and it accepts three parameters. The third parameter is the callback function. When the function executes, it spits out an alert message with the passed values displayed. Then it executes the callback function.

Notice that the actual parameter is just “callback” (without parentheses), but then when the callback is executed, it’s done using parentheses. You can call this parameter whatever you want, I just used “callback” so it’s obvious what’s going on.

The callback function itself is defined in the third argument passed to the function call. That code has another alert message to tell you that the callback code has now executed. You can see in this simple example that an argument passed into a function can be a function itself, and this is what makes callbacks possible in JavaScript.

Here’s a JSBin that uses the simple example above.

Make the Callback Optional

One thing you’ll notice about jQuery callbacks is that they’re optional. This means if a method accepts a callback, it won’t return an error if a callback is not included. In our simple example, the page will return an error if we call the function without a callback, like this:

<span class="hljs-function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span> <span class="hljs-title" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(239, 239, 143); background-position: 0px 0px;">mySandwich</span><span class="hljs-params" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">(param1, param2, callback)</span> </span>{    alert(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'Started eating my sandwich.\n\nIt has: '</span> + param1 + <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">', '</span> + param2);    callback();}mySandwich(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'ham'</span>, <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'cheese'</span>);

You can see this in action here. If you open your developer tools, you’ll see an error that says “undefined is not a function” (or something similar) that appears after the initial alert message.

To make the callback optional, we can just do this:

<span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span> <span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">mySandwich</span>(<span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">param1</span>, <span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">param2</span>, <span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">callback</span>) <span class="hljs-rules" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">{    <span class="hljs-rule" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-attribute" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">alert('Started eating my sandwich.\n\nIt has</span>:<span class="hljs-value" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;"> <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">' + param1 + '</span>, <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">' + param2);    if (callback) {        callback();    }}mySandwich('</span>ham<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">', '</span>cheese<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">');</span></span></span></span>

Now, since we’re checking to ensure the existence of callback, the function call won’t cause an error without it. You can test this example here.

Make Sure the Callback is a Function

Finally, you can ensure that whatever value is passed as the third argument is in fact a proper function, by doing this:

<span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span> <span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">mySandwich</span>(<span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">param1</span>, <span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">param2</span>, <span class="hljs-tag" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">callback</span>) <span class="hljs-rules" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">{    <span class="hljs-rule" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-attribute" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">alert('Started eating my sandwich.\n\nIt has</span>:<span class="hljs-value" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;"> <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">' + param1 + '</span>, <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">' + param2);    if (callback && typeof(callback) === "function") {        callback();    }}mySandwich('</span>ham<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">', '</span>cheese<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">', '</span>vegetables<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">');</span></span></span></span>

Notice that the function now includes a test using the typeof operator, to ensure that whatever is passed is actually a function. The function call has a third argument passed, but it’s not a function, it’s a string. So the test using typeof ensures no error occurs.

Here’s a JSBin with a nonfunction argument passed as the callback.

A Note About Timing

Although it is true that a callback function will execute last if it is placed last in the function, this will not always appear to happen. For example, if the function included some kind of asynchronous execution (like an Ajax call or an animation), then the callback would execute after the asynchronous action begins, but possibly before it finishes.

Here’s an example that uses jQuery’s animate method:

<span class="hljs-function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span> <span class="hljs-title" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(239, 239, 143); background-position: 0px 0px;">mySandwich</span><span class="hljs-params" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">(param1, param2, callback)</span> </span>{    alert(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'Started eating my sandwich.\n\nIt has: '</span> + param1 + <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">', '</span> + param2);      $(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'#sandwich'</span>).animate({        opacity: <span class="hljs-number" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(140, 208, 211); background-position: 0px 0px;">0</span>    }, <span class="hljs-number" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(140, 208, 211); background-position: 0px 0px;">5000</span>, <span class="hljs-function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span><span class="hljs-params" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">()</span> </span>{        <span class="hljs-comment" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(127, 159, 127); background-position: 0px 0px;">// Animation complete.</span>    });      <span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">if</span> (callback && <span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">typeof</span>(callback) === <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">"function"</span>) {        callback();    }}mySandwich(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'ham'</span>, <span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'cheese'</span>, <span class="hljs-function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;"><span class="hljs-keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(227, 206, 171); background-position: 0px 0px;">function</span><span class="hljs-params" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; background-position: 0px 0px;">()</span> </span>{     alert(<span class="hljs-string" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: baseline; color: rgb(204, 147, 147); background-position: 0px 0px;">'Finished eating my sandwich.'</span>);});

And again here’s that code live.

Notice that although the callback appears later in source order than the animation, the callback will actually execute long before the animation completes. In this case, solving this problem is easy: You just put the callback execution inside the animate method’s callback function (where it says “Animation complete”).

This doesn’t cover all the details regarding asynchronous functions, but it should serve as a basic warning that callback functions will only execute last as long as all the code in the function is synchronous.

Anything to Add?

For most JavaScript junkies, this is probably pretty easy stuff. So forgive me if you were expecting something deeper — this is the best I can do. :) But if you have anything else to add or want to correct anything I’ve said, please do so.

Advertise Here

96 Responses

  1. Caio Ribeiro Pereira:

    Nice post man!! It’s very useful and important to know how this callbacks works!

    Another things on Javascript to know is some Design patterns, look my blog there are some posts about it:

    http://www.udgwebdev.com/category/design-patterns/

    Reply
  2. Tyron:

    When I need to wait on multiple animates’ calbacks or ajax requests, inside the function that calls the callback (i.e. mySandwich), I tend to use Deferred objects to sync all those timings. I’ve modified your example to include this: http://jsbin.com/ajigan/edit#preview

    JavaScript callback function 理解

    看到segmentfault上的这个问题 JavaScript 回调函数怎么理解,觉得大家把异步和回调的概念混淆在一起了。做了回答:

    我觉得大家有点把回调(callback)和异步(asynchronous)的概念混淆在一起了。

    定义

    回调是什么?
    看维基的 Callback_(computer_programming) 条目:

    In computer programming, a callback is a reference to a piece of executable code that is passed as an argument to other code.

    jQuery文档How jQuery Works#Callback_and_Functio…条目:

    A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. The special thing about a callback is that functions that appear after the "parent" can execute before the callback executes. Another important thing to know is how to properly pass the callback. This is where I have often forgotten the proper syntax.

    百科:回调函数

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件条件进行响应。

    因此,回调本质上是一种设计模式,并且jQuery(包括其他框架)的设计原则遵循了这个模式。

    在JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。

    因此callback 不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

    例子

    一个同步(阻塞)中使用回调的例子,目的是在func1代码执行完成后执行func2。

    var func1=function(callback){    //do something.    (callback && typeof(callback) === "function") && callback();}func1(func2);    var func2=function(){}

    异步回调的例子:

    $(document).ready(callback);$.ajax({  url: "test.html",  context: document.body}).done(function() {   $(this).addClass("done");}).fail(function() { alert("error");}).always(function() { alert("complete"); });/**注意的是,ajax请求确实是异步的,不过这请求是由浏览器新开一个线程请求,当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理。见:http://www.phpv.net/html/1700.html*/

    回调什么时候执行

    回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。

    回调函数的使用场合

    • 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
    • DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。
    • setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
    • 链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现
    • setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。

    回调函数的传递

    上面说了,要将函数引用或者函数表达式作为参数传递。

    $.get('myhtmlpage.html', myCallBack);//这是对的$.get('myhtmlpage.html', myCallBack('foo', 'bar'));//这是错的,那么要带参数呢?$.get('myhtmlpage.html', function(){//带参数的使用函数表达式myCallBack('foo', 'bar');});

    另外,最好保证回调存在且必须是函数引用或者函数表达式:
    (callback && typeof(callback) === "function") && callback();

    This entry was posted in javascript by 猫少年. Bookmark the permalink.

    2 THOUGHTS ON “JAVASCRIPT CALLBACK FUNCTION 理解


0 0