[Go to site: main page, start]

0% found this document useful (0 votes)
3 views9 pages

Understanding JavaScript Callbacks

Uploaded by

orkuma.mike70
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views9 pages

Understanding JavaScript Callbacks

Uploaded by

orkuma.mike70
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

JavaScript Callbacks

Summary: in this tutorial, you will learn about JavaScript callback functions including
synchronous and asynchronous callbacks.

What are callbacks


In JavaScript, functions are first-class citizens. Therefore, you can pass a function to another
function as an argument.

By definition, a callback is a function that you pass into another function as an argument for
executing later.

The following defines a filter() function that accepts an array of numbers and returns a new
array of odd numbers:

function filter(numbers) {
let results = [];
for (const number of numbers) {
if (number % 2 != 0) {
[Link](number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
[Link](filter(numbers));

How it works.

First, define the filter() function that accepts an array of numbers and returns a new
array of the odd numbers.

Second, define the numbers array that has both odd and even numbers.

Third, call the filter() function to get the odd numbers out of the numbers array and
output the result.
If you want to return an array that contains even numbers, you need to modify the filter()
function. To make the filter() function more generic and reusable, you can:

First, extract the logic in the if block and wrap it in a separate function.

Second, pass the function to the filter() function as an argument.

Here’s the updated code:

function isOdd(number) {
return number % 2 != 0;
}

function filter(numbers, fn) {


let results = [];
for (const number of numbers) {
if (fn(number)) {
[Link](number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];
[Link](filter(numbers, isOdd));

The result is the same. However, you can pass any function that accepts an argument and returns
a boolean value to the second argument of the filter() function.

For example, you can use the filter() function to return an array of even numbers like this:

function isOdd(number) {
return number % 2 != 0;
}
function isEven(number) {
return number % 2 == 0;
}

function filter(numbers, fn) {


let results = [];
for (const number of numbers) {
if (fn(number)) {
[Link](number);
}
}
return results;
}
let numbers = [1, 2, 4, 7, 3, 5, 6];

[Link](filter(numbers, isOdd));
[Link](filter(numbers, isEven));

By definition, the isOdd and isEven are callback functions or callbacks. Because the
filter() function accepts a function as an argument, it’s called a high-order function.

A callback can be an anonymous function, which is a function without a name like this:

function filter(numbers, callback) {


let results = [];
for (const number of numbers) {
if (callback(number)) {
[Link](number);
}
}
return results;
}

let numbers = [1, 2, 4, 7, 3, 5, 6];

let oddNumbers = filter(numbers, function (number) {


return number % 2 != 0;
});

[Link](oddNumbers);

In this example, we pass an anonymous function to the filter() function instead of using a
separate function.

In ES6, you can use an arrow function like this:

function filter(numbers, callback) {


let results = [];
for (const number of numbers) {
if (callback(number)) {
[Link](number);
}
}
return results;
}

let numbers = [1, 2, 4, 7, 3, 5, 6];

let oddNumbers = filter(numbers, (number) => number % 2 != 0);

[Link](oddNumbers);

There are two types of callbacks: synchronous and asynchronous callbacks.

Synchronous callbacks
A synchronous callback is executed during the execution of the high-order function that uses the
callback. The isOdd and isEven are examples of synchronous callbacks because they execute
during the execution of the filter() function.

Asynchronous callbacks
An asynchronous callback is executed after the execution of the high-order function that uses the
callback.

Asynchronicity means that if JavaScript has to wait for an operation to complete, it will execute
the rest of the code while waiting.

Note that JavaScript is a single-threaded programming language. It carries asynchronous


operations via the callback queue and event loop.

Suppose that you need to develop a script that downloads a picture from a remote server and
processes it after the download completes:

function download(url) {
// ...
}

function process(picture) {
// ...
}

download(url);
process(picture);

However, downloading a picture from a remote server takes time depending on the network
speed and the size of the picture.

The following download() function uses the setTimeout() function to simulate the network
request:

function download(url) {
setTimeout(() => {
// script to download the picture here
[Link](`Downloading ${url} ...`);
},1000);
}

And this code emulates the process() function:

function process(picture) {
[Link](`Processing ${picture}`);
}

When you execute the following code:

let url = '[Link]

download(url);
process(url);

you will get the following output:

Processing [Link]
Downloading [Link] ...

This is not what you expected because the process() function executes before the download()
function. The correct sequence should be:
Download the picture and wait for the download complete.

Process the picture.

To resolve this issue, you can pass the process() function to the download() function and
execute the process() function inside the download() function once the download completes,
like this:

function download(url, callback) {


setTimeout(() => {
// script to download the picture here
[Link](`Downloading ${url} ...`);

// process the picture once it is completed


callback(url);
}, 1000);
}

function process(picture) {
[Link](`Processing ${picture}`);
}

let url = '[Link]


download(url, process);

Output:

Downloading [Link] ...


Processing [Link]

Now, it works as expected.

In this example, the process() is a callback passed into an asynchronous function.

When you use a callback to continue code execution after an asynchronous operation, the
callback is called an asynchronous callback.

To make the code more concise, you can define the process() function as an anonymous
function:
function download(url, callback) {
setTimeout(() => {
// script to download the picture here
[Link](`Downloading ${url} ...`);
// process the picture once it is completed
callback(url);

}, 1000);
}

let url = '[Link]


download(url, function(picture) {
[Link](`Processing ${picture}`);
});

Handling errors

The download() function assumes that everything works fine and does not consider any
exceptions. The following code introduces two callbacks: success and failure to handle the
success and failure cases respectively:

function download(url, success, failure) {


setTimeout(() => {
[Link](`Downloading the picture from ${url} ...`);
!url ? failure(url) : success(url);
}, 1000);
}

download(
'',
(url) => [Link](`Processing the picture ${url}`),
(url) => [Link](`The '${url}' is not valid`)
);

Nesting callbacks and the Pyramid of Doom

How do you download three pictures and process them sequentially? A typical approach is to call
the download() function inside the callback function, like this:
function download(url, callback) {
setTimeout(() => {
[Link](`Downloading ${url} ...`);
callback(url);
}, 1000);
}

const url1 = '[Link]


const url2 = '[Link]
const url3 = '[Link]

download(url1, function (url) {


[Link](`Processing ${url}`);
download(url2, function (url) {
[Link](`Processing ${url}`);
download(url3, function (url) {
[Link](`Processing ${url}`);
});
});
});

Output:

Downloading [Link] ...


Processing [Link]
Downloading [Link] ...
Processing [Link]
Downloading [Link] ...
Processing [Link]

The script works perfectly fine.

However, this callback strategy does not scale well when the complexity grows significantly.

Nesting many asynchronous functions inside callbacks is known as the pyramid of doom or the
callback hell:

asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
asyncFunction(function(){
....
});
});
});
});
});

To avoid the pyramid of doom, you use promises or async/await functions.

Summary
A callback is a function passed into another function as an argument to be executed
later.

A high-order function is a function that accepts another function as an argument.

Callback functions can be synchronous or asynchronous.

Common questions

Powered by AI

Synchronous callbacks execute during the execution of the higher-order function, blocking further operations until completion. An example can be seen in the use of the filter function with isOdd or isEven callback functions. They are called within the execution phase of filter . Asynchronous callbacks, on the other hand, execute after the execution of the higher-order function, often used in scenarios where you perform asynchronous operations, like fetching data over a network. These callbacks do not block further code execution allowing it to continue while waiting for an event. An example is provided with the download function, simulating a network request with setTimeout and calling process as the callback function once download completes .

Callbacks can lead to 'callback hell' or 'pyramid of doom' when multiple asynchronous operations are nested within each other, causing the code to become difficult to read and maintain. This commonly occurs when you have several callback functions nested in such a way to handle asynchronous logic sequentially. To resolve this issue, modern JavaScript introduces promises and async/await constructs. Promises provide a more structured chain to handle asynchronous operations by eliminating deep nesting through method chaining. The async/await syntax allows writing asynchronous code in a synchronous manner, further reducing complexity and improving readability .

JavaScript higher-order functions are functions that accept other functions as arguments. This paradigm allows for greater modularity and flexibility in coding as it promotes write once, use anywhere logic. For example, the filter function can be transformed into a higher-order function by passing in different callback functions (like isOdd or isEven) to perform various filtering operations. This makes your code adaptable to different requirements without changing the higher-order function's base structure, enabling reusability and separation of concerns .

JavaScript's single-threaded nature means it executes one command at a time in the sequence it's given. To handle asynchronous operations, JavaScript uses the event loop and callback queue. The event loop monitors the call stack and the callback queue, pushing tasks to the stack when it's empty and when certain conditions are met, allowing JavaScript to perform asynchronous operations efficiently without interrupting the execution of other operations. A typical method to handle asynchronous tasks in JavaScript is using the setTimeout function to simulate delayed operations and perform tasks once other operations are complete, minimizing blockages and promoting continuous execution .

To increase the reusability of the filter function, you can extract the logic within the if block to a separate function, then pass this function as an argument to the filter function. For instance, instead of hardcoding the condition to filter out odd numbers, you can write separate callback functions like isOdd or isEven, and pass these as arguments to filter. This way, the filter function can be used to filter arrays based on any condition given by the callback. For example: ```javascript function isOdd(number) { return number % 2 != 0; } function filter(numbers, fn) { let results = []; for (const number of numbers) { if (fn(number)) { results.push(number); } } return results; } ```` You then use the filter function with different callbacks like `isOdd` or `isEven` depending on the context .

In JavaScript, two callbacks can be introduced to handle success and failure scenarios during asynchronous operations. When performing a task such as downloading data from a server, you can create two separate callback functions: one that handles the success case, processing the data once it's downloaded, and another to handle failure scenarios like an invalid URL. For instance, within a download function, you can pass a success function and a failure function as arguments and invoke them based on the occurrence of successfully downloaded data or an error .

Callback functions enhance the utility of the JavaScript filter function by enabling it to execute various filtering logic without altering the function's core structure. By accepting a function as an argument, the filter function can apply different conditions passed in as callbacks to determine which elements to include in the resulting array. For arrays, it allows operations like filtering odd or even numbers or any other complex conditions by simply substituting different callback functions, thus making it a versatile tool for array manipulation and data processing .

Using anonymous functions as callbacks in JavaScript makes the code more concise by eliminating the need to declare separate named functions for simple operations that are not reused elsewhere. This is especially useful when you need to pass a simple inline logic that is self-contained, such as filtering or sorting data temporarily. Anonymous functions allow you to define this logic directly inline as a callback, streamlining the code and reducing verbosity, as shown in the uses of the filter function where anonymous functions replace named callback functions for simple tasks .

Promises and async/await address the limitations of straight callback chains, such as callback hell, by providing a more organized, readable, and manageable way to handle asynchronous operations. Promises allow chaining operations with .then() and .catch(), reducing the nesting levels of callbacks and handling errors more systematically. The async/await syntax further simplifies asynchronous flows by enabling asynchronous code to be written in a linear, synchronous-looking style, making it easier to understand and maintain compared to deeply nested callbacks. These features eliminate complexity, improve error handling, and enhance maintainability in complex applications .

Separating the logic of a filter function into a callback aligns with principles of functional programming by promoting pure functions and higher-order functions. In functional programming, functions should ideally have no side effects and determine outputs solely based on their inputs. By encapsulating the filter condition into callback functions, you adhere to these principles by creating reusable, composable units that can be easily plugged into higher-order functions like filter. This practice encourages a separation of concerns, enhancing modularity and making the code more predictable and easier to test .

You might also like