Open In App

How “Control Flow” Controls the Functions Calls ?

Last Updated : 08 Jun, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Node.js is a popular open-source platform for building scalable and high-performance web applications. One of the key features of Node.js is its ability to handle large numbers of concurrent connections efficiently. One of the underlying mechanisms that make this possible is the concept of control flow – a way of managing the flow of function calls in a Node.js program.

In Node.js, functions are often executed asynchronously. This means that a function does not block the execution of the program while it is running, but instead returns control back to the event loop. The event loop is a key component of Node.js that manages the execution of functions and callbacks.

Control flow in Node.js is typically managed using one of three methods: callbacks, promises, and async/await.

1. Callbacks: Callbacks are functions that are passed as arguments to other functions and are executed when that function completes its task. In Node.js, many functions are asynchronous, and they often take a callback as an argument. When the asynchronous operation is completed, the callback is executed, allowing the program to continue its execution.

Steps to use a callback:

  1. Define the function that accepts a callback parameter. This function should perform some asynchronous operation and then call the callback with the result.
  2. When calling the function, pass a callback function as an argument. This callback will be executed once the asynchronous operation is complete.
  3. Within the callback function, you can handle the result of the asynchronous operation.

Syntax:

function functionName(param1, param2, callback){
    // Asynchronous operation
    callback(result);
}

Example 1:

Javascript




const multiply = (a, b, callback) => {
  
    // Multiply the values of a and b
    const result = a * b;
  
    // Pass the result to the callback function
    callback(result); 
}
  
multiply(2, 3, (result) => {
  
    // Log "Callback" to the console
    console.log("Callback"); 
  
    // Log the result with a prefix "Ans: "
    console.log("Ans: " + result); 
});


Output

Callback
Ans: 6

In the above example, the multiply function takes two numbers as arguments and a callback function that is executed when the multiplication is complete. The callback function simply logs the result to the console. The arrow function syntax is used to define the multiply function and the callback function.

Example 2:

Javascript




// Function that performs an asynchronous operation
// and invokes a callback when done
const fetchData = (url, callback) => {
  
    // Simulating an asynchronous operation 
    // (e.g., making an API request)
    setTimeout(() => {
        const data = { id: 74, name: "Geeks for Geeks" };
        callback(data);
    }, 2000); // Simulating a 2-second delay
}
  
// Callback function that handles the fetched data
const handleData = (data) => {
    console.log("Fetched data:", data);
}
  
// Call the fetchData function with the
// URL and the callback function
fetchData("https://example.com/api/data", handleData);


Output

Fetched data: { id: 74, name: 'Geeks for Geeks' }

In the above example, the fetchData function simulates an asynchronous operation (like making an API request) by using setTimeout. After the simulated delay, it invokes the callback function callback with the fetched data.

The callback function handleData receives the fetched data and performs some operations with it. In this case, it logs the data to the console. When we call the fetchData function, we pass the URL, and the callback function handleData.

2. Promises: Promises are a way to handle asynchronous operations in JavaScript. They provide an alternative approach to callbacks, making it easier to write and manage asynchronous code. Promises represent a value that may not be available yet but will be available in the future. When the value becomes available, the promise is fulfilled, and the associated code is executed. This allows for more efficient and flexible handling of asynchronous operations and helps to prevent callback hell. Promises are created using the Promise constructor, which takes a function that defines the operation to be performed. Once the operation is complete, the promise is resolved or rejected, and the appropriate code is executed. Promises can be chained together using the then() method, allowing for more complex workflows to be defined.

Steps to define a promise:

  1. Create a Promise using the Promise constructor, which takes a function with resolve and rejects parameters.
  2. Use the resolve function to fulfill the promise with a value or the reject function to reject the promise with an error.
  3. Handle the fulfilled and rejected states of the promise using the .then() and .catch() methods.

Syntax:

function functionName(){
    return new Promise(function(resolve, reject){
        // Operation 
    });
}

functionName()
    .then(result)
    .catch(error);

Example 1:

Javascript




// Function that divides two numbers
// and returns a promise
function divide(a, b) {
    return new Promise(function (resolve, reject) {
        if (b === 0) {
  
            // Reject the promise with an
            // error if b is zero
            reject(new Error("Cannot divide by zero"));
        } else {
            // Resolve the promise with 
            // the result of the division
            resolve(a / b);
        }
    });
}
  
// Call the divide function with arguments 10 and 2
divide(10, 2)
    .then(function (result) {
  
        // Log "Promise" to the console
        console.log("Promise"); 
  
        // Log the result with a prefix "Ans: "
        console.log("Ans: " + result); 
    })
    .catch(function (error) {
  
        // Log any errors that occurred
        // during the division
        console.log(error); 
    });


Output

Promise
Ans: 5

In the above example, the divide function returns a promise that resolves to the result of dividing a by b. If b is zero, the promise is rejected with an error message. Then the promise is used in the then method to handle the fulfillment of the promise (i.e. when the result is available) and the catch method is used to handle the rejection of the promise (i.e. when an error occurs). In this case, the result of the division is logged into the console.

Example 2:

Javascript




// Function that performs an asynchronous
// operation and returns a promise
function getUser(id) {
    return new Promise(function (resolve, reject) {
  
        // Simulating an asynchronous 
        // operation (e.g., fetching
        // user data from a database)
        setTimeout(() => {
            const users = {
                1: { id: 73, name: "Geek" },
                2: { id: 74, name: "Geeks for Geeks" },
                3: { id: 75, name: "M." }
            };
  
            if (users[id]) {
  
                // Resolve the promise with
                // the user data if found
                resolve(users[id]);
            } else {
  
                // Reject the promise with an
                // error if user not found
                reject(new Error("User not found"));
            }
        }, 2000); // Simulating a 2-second delay
    });
}
  
// Call the getUser function with user ID 2 and 
//handle the promise using then and catch
getUser(2)
    .then(function (user) {
  
        // Log the retrieved user data to
        // the console
        console.log("User:", user);
    })
    .catch(function (error) {
  
        // Log any errors that occurred during
        // the asynchronous operation
        console.log("Error:", error);
    });


Output

User: { id: 74, name: 'Geeks for Geeks' }

In the above example, the getUser function takes a user ID as an argument and returns a promise. Inside the promise, we simulate an asynchronous operation by using setTimeout. After the simulated delay, we check if the user with the given ID exists in our sample user data.

If the user is found, we resolve the promise with the user data. If the user is not found, we reject the promise with an error.

We call the getUser function with user ID 2 and handle the promise using the then and catch methods. The then method is used to handle the resolved promise and receive the user data. The catch method is used to handle any errors that occurred during the asynchronous operation.

When the promise is resolved, we log the retrieved user data to the console. If there are any errors, we log the error message instead.

3. Async/await: Async/await is a language feature in Node.js that allows developers to write asynchronous code that looks similar to synchronous code. It is considered a more elegant and readable way to handle asynchronous operations than using callbacks or promises.

By marking a function with the async keyword, it becomes an asynchronous function that always returns a promise. The await keyword can be used inside an async function to pause its execution until the promise it is waiting for resolves or rejects. The value returned by the promise is then returned by the async function.

This makes it possible to write asynchronous code that looks like synchronous code, making it easier to understand and maintain. It also avoids the problem of callback hell that can occur when working with nested callbacks.

Some steps to use an Async/await:

  1. Define an asynchronous function using the async keyword before the function declaration.
  2. Within the asynchronous function, use the await keyword to pause the function until a promise is fulfilled or rejected.
  3. When using await, make sure the expression following it is a promise. If it is not a promise, it will be implicitly converted to a resolved promise with the value of the expression.
  4. Use try-catch block to handle any errors that may be thrown by the awaited promise.
  5. Call the asynchronous function and handle the resolved or rejected promise using .then() and .catch() as you would with regular promises.

Syntax:

async function functionName(){
    await wait(ms); 
}

functionName().catch(() => {});

Example 1:

Javascript




// Function that returns a promise that
// resolves after a specified time
function wait(time) {
    return new Promise(
        resolve => setTimeout(resolve, time));
}
  
// Async function that demonstrates
// the usage of async/await
async function example() {
  
    // Log a message indicating the 
    // start of the async function
    console.log("Async/await Starting...");
  
    // Wait for 1 second
    await wait(1000); 
  
    // Log a message after 1 second has passed
    console.log("One second has passed!");
  
    // Wait for 2 seconds
    await wait(2000); 
  
    // Log a message after 2 more
    // seconds have passed
    console.log("Two more seconds have passed!");
}
  
// Call the async function to start
// the execution
example();


Output

Async/await Starting...
One second has passed!
Two more seconds have passed!

In the above example, we define a wait function that returns a promise that resolves after a given amount of time (in milliseconds). We then define an example async function that uses await to pause execution until the promises returned by the wait function have resolved. When the example is called, it logs a message, waits for one second, logs another message, waits for two more seconds, and logs a final message. Because we are using async and await, we don’t have to use callbacks or chain promises together, which can make our code more readable and easier to reason about.

Example 2:

Javascript




// Async function that adds two
// numbers and returns the sum
async function add(a, b) {
  
    // Add the numbers a and b
    const sum = a + b; 
    return sum; // Return the sum
}
  
// Async function that demonstrates
// the usage of async/await
async function run() {
  
    // Await the result of adding 2 and 3
    const result = await add(2, 3);
  
    // Log a message indicating the 
    // use of async/await
    console.log("Async/await");
  
    // Log the sum with a prefix "Sum is: "
    console.log("Sum is: " + result);
}
  
// Call the async function to
// start the execution
run();


Output

Async/await
Sum is: 5

In the above example, the add function is marked as async, indicating that it returns a promise. The run function is also marked as async, and it uses the await keyword to wait for the promise returned by add to resolve before continuing execution.

Overall, in Node.js, the control flow of function calls can be managed using callbacks, promises, or async/await, depending on the specific needs and requirements of the program. These mechanisms provide a powerful and flexible way of managing the flow of function calls, allowing Node.js developers to build highly efficient and scalable applications.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads