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:
- Define the function that accepts a callback parameter. This function should perform some asynchronous operation and then call the callback with the result.
- When calling the function, pass a callback function as an argument. This callback will be executed once the asynchronous operation is complete.
- 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) => {
const result = a * b;
callback(result);
}
multiply(2, 3, (result) => {
console.log( "Callback" );
console.log( "Ans: " + result);
});
|
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
const fetchData = (url, callback) => {
setTimeout(() => {
const data = { id: 74, name: "Geeks for Geeks" };
callback(data);
}, 2000);
}
const handleData = (data) => {
console.log( "Fetched data:" , data);
}
|
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:
- Create a Promise using the Promise constructor, which takes a function with resolve and rejects parameters.
- Use the resolve function to fulfill the promise with a value or the reject function to reject the promise with an error.
- 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 divide(a, b) {
return new Promise( function (resolve, reject) {
if (b === 0) {
reject( new Error( "Cannot divide by zero" ));
} else {
resolve(a / b);
}
});
}
divide(10, 2)
.then( function (result) {
console.log( "Promise" );
console.log( "Ans: " + result);
})
. catch ( function (error) {
console.log(error);
});
|
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 getUser(id) {
return new Promise( function (resolve, reject) {
setTimeout(() => {
const users = {
1: { id: 73, name: "Geek" },
2: { id: 74, name: "Geeks for Geeks" },
3: { id: 75, name: "M." }
};
if (users[id]) {
resolve(users[id]);
} else {
reject( new Error( "User not found" ));
}
}, 2000);
});
}
getUser(2)
.then( function (user) {
console.log( "User:" , user);
})
. catch ( function (error) {
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:
- Define an asynchronous function using the async keyword before the function declaration.
- Within the asynchronous function, use the await keyword to pause the function until a promise is fulfilled or rejected.
- 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.
- Use try-catch block to handle any errors that may be thrown by the awaited promise.
- 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 wait(time) {
return new Promise(
resolve => setTimeout(resolve, time));
}
async function example() {
console.log( "Async/await Starting..." );
await wait(1000);
console.log( "One second has passed!" );
await wait(2000);
console.log( "Two more seconds have passed!" );
}
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 add(a, b) {
const sum = a + b;
return sum;
}
async function run() {
const result = await add(2, 3);
console.log( "Async/await" );
console.log( "Sum is: " + result);
}
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
Share your thoughts in the comments
Please Login to comment...