JavaScript functions are a fundamental part of the language, enabling you to encapsulate code for reuse, organization, and abstraction. Standardizing functions in JavaScript involves using consistent practices and patterns to ensure code is readable, maintainable, and efficient. Here are key aspects of JavaScript function standardization, along with detailed examples:

Table of Contents

  1. Beginner Level
  2. Intermediate Level
  3. Advanced Level

Beginner Level

Basic Function Declaration

A basic function in JavaScript is used to encapsulate code into a reusable block. This function can be called with various arguments to perform a specific task.

// Syntax 

function functionName() {
  // code to be executed
}

// Function to greet  a person
function greet() {
  // Output a greeting message to the console
  console.log("Hello I am learning JavaScript Functions!");
}

// Calling the function
greet(); // Output: Hello I am learning JavaScript Functions!

// Syntax with Parameters

function functionNameWithParam(parameters) {
  // code to be executed
}

// Function to greetWithParam a person
function greetWithParam(name) {
  // Output a greeting message to the console
  console.log("Hello, " + name + "!");
}

// Calling the function with the argument "Alice"
greetWithParam("Alice"); // Output: Hello, Alice!
Explanation:
  • function greetWithParam(name) declares a function named greetWithParam with a parameter name.
  • console.log("Hello, " + name + "!"); prints a greeting message to the console.
  • greetWithParam("Alice"); calls the function with the argument "Alice", resulting in Hello, Alice!.

Overview of return

In JavaScript, the return statement is used within a function to specify the value that the function should return to the caller. Once a return statement is executed, the function’s execution stops, and the specified value is sent back to where the function was called. If no return statement is present, the function returns undefined by default.

// Syntax:
return [expression];
/*
Purpose:
1. To return a value from a function.
2. To terminate the function execution and exit early.
*/

// Detailed Examples
// Example 1: Basic Return

// Function to add two numbers
function add(a, b) {
  return a + b;
}

// Calling the function and storing the result
const result = add(3, 5);

// Output the result
console.log(result); // Output: 8

/*
Explanation:
1. function add(a, b) { return a + b; } defines a function that returns the sum of two parameters.
2. const result = add(3, 5); calls the function and stores the returned value in result.
3. console.log(result); outputs the returned value.
*/

// Example 2: Early Return

// Function to check if a number is positive
function isPositive(num) {
  if (num <= 0) {
    return false; // Exit early if the number is not positive
  }
  return true; // Return true if the number is positive
}

// Calling the function with different values
console.log(isPositive(10)); // Output: true
console.log(isPositive(-5)); // Output: false
console.log(isPositive(0));  // Output: false

/*
Explanation:
1. if (num <= 0) { return false; } exits the function early if the condition is met.
2. return true; is executed only if the number is positive.
*/

// Example 3: Returning Objects

// Function to create a user object
function createUser(name, age) {
  return {
    name: name,
    age: age
  };
}

// Calling the function and storing the user object
const user = createUser("Alice", 30);

// Output the user object
console.log(user); // Output: { name: 'Alice', age: 30 }

/*
Explanation:
1. return { name: name, age: age }; returns an object with name and age properties.
*/

// Example 4: Returning Multiple Values

// Function to get the maximum and minimum values from an array
function getMinMax(arr) {
  return {
    min: Math.min(...arr),
    max: Math.max(...arr)
  };
}

// Calling the function with an array
const result = getMinMax([4, 2, 8, 6]);

// Output the result
console.log(result); // Output: { min: 2, max: 8 }

/*
Explanation:
1. return { min: Math.min(...arr), max: Math.max(...arr) }; returns an object containing both the minimum and maximum values.
*/

// Example 5: No Return Statement

// Function without a return statement
function logMessage(message) {
  console.log(message);
}

// Calling the function
const result = logMessage("Hello, World!");

// Output the result
console.log(result); // Output: undefined

/*
Explanation:
1. function logMessage(message) { console.log(message); } does not return any value explicitly.
2. result will be undefined because no value is returned from the function.
*/


/*
Best Practices:-
1. Always Use Return to Provide Output: If you intend for your function to output a value, use the return statement explicitly.
2. Early Returns for Readability: Use early returns to simplify logic and avoid deep nesting.
3. Consistent Return Types: Ensure that functions consistently return values of the expected type.
4. Avoid Unreachable Code: Code placed after a return statement in a function is never executed. Make sure your code is reachable and meaningful.

Understanding and using return effectively allows you to create functions that are modular, predictable, and easy to maintain.
*/
Example Addition Function –
// Function to add two numbers
function add(a, b) {
  // Return the sum of the two numbers
  return a + b;
}
// Calling the function with arguments 3 and 4
console.log(add(3, 4)); // Output: 7
  1. Explanation
    • function add(a, b) defines a function that takes two parameters, a and b.
    • return a + b; returns the sum of the parameters.
    • console.log(add(3, 4)); calls the function with arguments 3 and 4, outputting 7.
  2. Output: 7
Example Subtracting Two Numbers –
// Function to subtract one number from another
function subtract(x, y) {
  // Return the difference between the two numbers
  return x - y;
}

// Calling the function with arguments 10 and 5
console.log(subtract(10, 5)); // Output: 5
  1. Explanation
    • function subtract(x, y) defines a function that subtracts y from x.
    • return x - y; returns the difference between the parameters.
    • console.log(subtract(10, 5)); calls the function with 10 and 5, resulting in 5.
  2. Output: 7
Example Concatenating Strings –
// Function to concatenate two strings
function concatenate(str1, str2) {
  // Return the concatenated result of the two strings
  return str1 + str2;
}

// Calling the function with arguments "Hello, " and "World!"
console.log(concatenate("Hello, ", "World!")); // Output: Hello, World!
  1. Explanation
    • function concatenate(str1, str2) defines a function to concatenate two strings.
    • return str1 + str2; returns the combined result of str1 and str2.
    • console.log(concatenate(“Hello, “, “World!”)); results in Hello, World!.
  2. Output: Hello, World!
Example Check Even or Odd –
// Function to check if a number is even
function isEven(num) {
  // Return true if the number is even, otherwise false
  return num % 2 === 0;
}

// Calling the function with arguments 4 and 7
console.log(isEven(4)); // Output: true
console.log(isEven(7)); // Output: false
  1. Explanation
    • function isEven(num) checks if a number is even.
    • return num % 2 === 0; returns true if num is even, otherwise false.
    • console.log(isEven(4)); and console.log(isEven(7)); show true and false respectively.

Function Expressions

Function expressions are used to create functions that are assigned to variables. These functions can be anonymous or named and offer flexibility in functional programming.

// Example 1: Function Expression for Multiplication

// Function expression to multiply two numbers
const multiply = function(a, b) {
  // Return the product of the two numbers
  return a * b;
};

// Calling the function with arguments 2 and 5
console.log(multiply(2, 5)); // Output: 10

/*
Explanation:
1. const multiply = function(a, b) creates an anonymous function assigned to multiply.
2. return a * b; returns the product of a and b.
3. console.log(multiply(2, 5)); results in 10.
*/

// Example 2: Function Expression with Return

// Function expression to square a number
const square = function(num) {
  // Return the square of the number
  return num * num;
};

// Calling the function with the argument 6
console.log(square(6)); // Output: 36

/*
Explanation:
1. const square = function(num) defines an anonymous function that returns the square of a number.
2. console.log(square(6)); results in 36.
*/

// Example 3: Named Function Expression

// Named function expression to divide two numbers
const divide = function division(a, b) {
  // Check for division by zero
  if (b === 0) return "Cannot divide by zero";
  // Return the quotient of the two numbers
  return a / b;
};

// Calling the function with arguments 8 and 2, and 8 and 0
console.log(divide(8, 2)); // Output: 4
console.log(divide(8, 0)); // Output: Cannot divide by zero
/*
Explanation:
1. const divide = function division(a, b) creates a named function expression division.
2. return a / b; returns the quotient of a and b, handling division by zero.
*/

//Example 4: Function Expression with Conditional

// Function expression to find the maximum of two numbers
const max = function(a, b) {
  // Return the larger of the two numbers using a ternary operator
  return a > b ? a : b;
};

// Calling the function with arguments 3 and 7
console.log(max(3, 7)); // Output: 7
/*
Explanation:
1. const max = function(a, b) defines a function that returns the larger of a and b.
2. return a > b ? a : b; uses a ternary operator to determine the maximum.
*/

// Example 5: Function Expression with Closures
// Function expression to create a counter
const makeCounter = function() {
  let count = 0; // Private variable
  // Return a function that increments and returns the count
  return function() {
    count += 1;
    return count;
  };
};

// Create a counter instance
const counter = makeCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

/*
Explanation:
1. const makeCounter = function() creates a function that returns an inner function.
2. The inner function maintains access to the count variable, incrementing it each time it's called.
*/

Intermediate Level

Arrow Functions

Arrow functions provide a concise syntax for writing functions and do not have their own this context, making them ideal for callbacks and functional programming.

Example 1: Basic Arrow Function
// Arrow function to add two numbers
const add = (a, b) => a + b;

// Calling the arrow function with arguments 2 and 3
console.log(add(2, 3)); // Output: 5
  • Explanation – const add = (a, b) => a + b; uses arrow syntax to define a function that adds two numbers.
  • Output – 5
Example 2: Arrow Function with No Parameters
// Arrow function with no parameters
const greet = () => console.log("Hello, World!");

// Calling the arrow function
greet(); // Output: Hello, World!
  • Explanationconst greet = () => console.log("Hello, World!"); defines an arrow function that logs a greeting message.
  • Output – Hello, World!
Example 3: Arrow Function with One Parameter
// Arrow function with one parameter
const square = x => x * x;

// Calling the arrow function with the argument 4
console.log(square(4)); // Output: 16
  • Explanationconst square = x => x * x; defines an arrow function that squares a number.
  • Output – 16
Example 4: Arrow Function with Multiple Statements
// Arrow function with multiple statements
const calculate = (a, b) => {
  let sum = a + b;
  let difference = a - b;
  return { sum, difference };
};

// Calling the arrow function with arguments 10 and 5
console.log(calculate(10, 5)); // Output: { sum: 15, difference: 5 }
  • Explanationconst calculate = (a, b) => { ... } uses curly braces to define a function with multiple statements.
  • Output - { sum: 15, difference: 5 }
Example 5: Arrow Function for Array Mapping
// Array of numbers
const numbers = [1, 2, 3, 4];

// Arrow function to double each number
const doubled = numbers.map(num => num * 2);

// Output the doubled array
console.log(doubled); // Output: [2, 4, 6, 8]
  • Explanationconst doubled = numbers.map(num => num * 2); uses an arrow function to map each number in the array to its double.
  • Output – [2, 4, 6, 8]

Default Parameters

Default parameters allow functions to be called with fewer arguments than defined, using default values for any missing parameters.

// Example 1: Default Greeting

// Function to greet a person with a default name
function greet(name = "Guest") {
  // Output a greeting message to the console
  console.log("Hello, " + name + "!");
}

// Calling the function without arguments
greet(); // Output: Hello, Guest!

/*
Explanation: function greet(name = "Guest") sets "Guest" as the default value for name.
Output: Hello, Guest!
*/

// Example 2: Default Age

// Function to introduce a person with a default age
function introduce(name, age = 25) {
  // Output the introduction message
  console.log(name + " is " + age + " years old.");
}

// Calling the function with only the name
introduce("John"); // Output: John is 25 years old.

/*
Explanation: function introduce(name, age = 25) sets 25 as the default value for age.
Output: John is 25 years old.
*/

// Example 3: Default Value with Addition 

// Function to add two numbers with a default second parameter
function add(a, b = 10) {
  // Return the sum of the two numbers
  return a + b;
}

// Calling the function with one argument
console.log(add(5)); // Output: 15

/*
Explanation: function add(a, b = 10) sets 10 as the default value for b.
Output: 15
*/

// Example 4: Default Parameters in Object

// Function to create a user with default name and age
function createUser({ name = "Anonymous", age = 0 } = {}) {
  // Return a formatted string with user details
  return `Name: ${name}, Age: ${age}`;
}

// Calling the function with and without arguments
console.log(createUser()); // Output: Name: Anonymous, Age: 0
console.log(createUser({ name: "Alice", age: 30 })); // Output: Name: Alice, Age: 30

/*
Explanation: function createUser({ name = "Anonymous", age = 0 } = {}) uses default values within an object.
Output: Name: Anonymous, Age: 0
        Name: Alice, Age: 30
*/

// Example 5: Default Parameter with Function Call

// Function to calculate discount with a default discount amount
function calculateDiscount(price, discount = calculateDefaultDiscount()) {
  // Return the price after applying the discount
  return price - discount;
}

// Function to return the default discount amount
function calculateDefaultDiscount() {
  return 5; // Default discount amount
}

// Calling the function with one argument
console.log(calculateDiscount(100)); // Output: 95

/*
Explanation: function calculateDiscount(price, discount = calculateDefaultDiscount()) uses a function to determine the default value for discount.
Output: 95
*/

Rest Parameters

Rest parameters allow a function to accept an indefinite number of arguments as an array. This is useful for handling variable numbers of arguments.

// Example 1: Sum of Numbers

// Function to calculate the sum of any number of arguments
function sum(...numbers) {
  // Reduce the array to a single sum value
  return numbers.reduce((total, num) => total + num, 0);
}

// Calling the function with multiple arguments
console.log(sum(1, 2, 3)); // Output: 6
console.log(sum(4, 5, 6, 7)); // Output: 22

/*
Explanation: 
1. function sum(...numbers) uses rest parameters to accept multiple arguments.
2. numbers.reduce((total, num) => total + num, 0) calculates the sum of the arguments.

Output: 6
        12
*/

// Example 2: Concatenating Strings

// Function to concatenate multiple strings
function concatenate(...strings) {
  // Join all strings into a single string
  return strings.join(' ');
}

// Calling the function with multiple strings
console.log(concatenate("Hello", "world!", "How", "are", "you?")); // Output: Hello world! How are you?


// Example 3: Collecting Arguments

// Function to collect and return arguments as an array
function collectArgs(...args) {
  // Return the array of arguments
  return args;
}

// Calling the function with various arguments
console.log(collectArgs(1, 'a', true, [2, 3])); // Output: [1, 'a', true, [2, 3]]

/*
Explanation: 
1. function collectArgs(...args) collects all arguments into an array.
2. return args; returns the array of arguments.

Output: [1, 'a', true, [2, 3]]
*/

// Example 4: Handling Variable Arguments

// Function to display arguments
function displayArgs(message, ...args) {
  console.log(message);
  console.log('Arguments:', args);
}

// Calling the function with a message and multiple arguments
displayArgs('Here are your arguments:', 'one', 'two', 'three'); 
// Output: Here are your arguments: Arguments: ['one', 'two', 'three']

/*
Explanation: 
1. function displayArgs(message, ...args) takes a message and a variable number of additional arguments.
2. console.log('Arguments:', args); displays the variable arguments.

Output:
Here are your arguments:
Arguments: ['one', 'two', 'three']
*/

// Example 5: Function with Rest Parameters and Spread Syntax

// Function to merge multiple arrays
function mergeArrays(...arrays) {
  // Use the spread operator to merge arrays into one
  return [].concat(...arrays);
}

// Calling the function with multiple arrays
console.log(mergeArrays([1, 2], [3, 4], [5, 6])); // Output: [1, 2, 3, 4, 5, 6]

/*
Explanation: 
1. function mergeArrays(...arrays) uses rest parameters to accept multiple arrays.
2. [].concat(...arrays) merges all arrays into one.

Output: [1, 2, 3, 4, 5, 6]
*/

Destructuring Parameters

Destructuring allows extracting values from arrays or properties from objects into distinct variables, providing a more readable way to handle complex data.

// Example 1: Array Destructuring

// Function to return the first and second elements of an array
function getFirstAndSecond([first, second]) {
  return `First: ${first}, Second: ${second}`;
}

// Calling the function with an array
console.log(getFirstAndSecond([10, 20])); // Output: First: 10, Second: 20

/*
Explanation:// Example 1: Array Destructuring

// Function to return the first and second elements of an array
function getFirstAndSecond([first, second]) {
  return `First: ${first}, Second: ${second}`;
}

// Calling the function with an array
console.log(getFirstAndSecond([10, 20])); // Output: First: 10, Second: 20

/*
Explanation:function getFirstAndSecond([first, second]) uses array destructuring to extract the first two elements.
Output: First: 10, Second: 20
*/

// Example 2: Object Destructuring

// Function to return name and age from an object
function introduce({ name, age }) {
  return `${name} is ${age} years old.`;
}

// Calling the function with an object
console.log(introduce({ name: 'Alice', age: 30 })); // Output: Alice is 30 years old.

/*
Explanation: function introduce({ name, age }) uses object destructuring to extract name and age.
Output: Alice is 30 years old.
*/

// Example 3: Destructuring with Default Values

// Function to return a greeting with a default name
function greet({ name = 'Guest', age = 0 }) {
  return `Hello, ${name}. You are ${age} years old.`;
}

// Calling the function with partial object
console.log(greet({ age: 25 })); // Output: Hello, Guest. You are 25 years old.

/*
Explanation:  function greet({ name = 'Guest', age = 0 }) uses default values for destructured properties.
Output: Hello, Guest. You are 25 years old.
*/

// Example 4: Nested Destructuring

// Function to return nested values from an object
function getFullName({ name: { first, last } }) {
  return `${first} ${last}`;
}

// Calling the function with a nested object
console.log(getFullName({ name: { first: 'John', last: 'Doe' } })); // Output: John Doe

/*
Explanation: function getFullName({ name: { first, last } }) uses nested destructuring to extract first and last names.
Output: John Doe
*/

// Example 5: Destructuring in Function Parameters

// Function to handle user details with nested destructuring
function handleUser({ id, details: { name, email } }) {
  return `ID: ${id}, Name: ${name}, Email: ${email}`;
}

// Calling the function with a nested object
console.log(handleUser({ id: 1, details: { name: 'Alice', email: 'alice@example.com' } })); 
// Output: ID: 1, Name: Alice, Email: alice@example.com

/*
Explanation: function handleUser({ id, details: { name, email } }) uses nested destructuring for detailed user information.
Output: ID: 1, Name: Alice, Email: alice@example.com
*/

Advanced Level

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or return functions as results. They are essential for functional programming.

// Example 1: Function Returning Another Function

// Function to create a greeting function with a specific greeting
function createGreeting(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };

}

// Create greeting functions
const helloGreeting = createGreeting("Hello");
const hiGreeting = createGreeting("Hi");

// Using the greeting functions
console.log(helloGreeting("Alice")); // Output: Hello, Alice!
console.log(hiGreeting("Bob")); // Output: Hi, Bob!

/*
Explanation: createGreeting(greeting) returns a function that generates personalized greetings.
Output: Hello, Alice!
        Hi, Bob!
*/

// Example 2: Function Taking Another Function as Argument

// Function to apply a given function to two numbers
function applyOperation(a, b, operation) {
  return operation(a, b);
}

// Define addition and multiplication functions
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;

// Applying operations
console.log(applyOperation(4, 5, add)); // Output: 9
console.log(applyOperation(4, 5, multiply)); // Output: 20

/*
Explanation: applyOperation(a, b, operation) applies the provided operation function to a and b.
Output: 9
        20
*/

// Example 3: Using Array Methods

// Array of numbers
const numbers = [1, 2, 3, 4, 5];

// Using map to double each number
const doubled = numbers.map(num => num * 2);

// Using filter to keep only even numbers
const evenNumbers = numbers.filter(num => num % 2 === 0);

// Output the results
console.log(doubled); // Output: [2, 4, 6, 8, 10]
console.log(evenNumbers); // Output: [2, 4]

/*
Explanation: map and filter are higher-order functions used to transform and filter arrays.
Output: [2, 4, 6, 8, 10]
        [2, 4]
*/

// Example 4: Function Composition

// Function to compose two functions
function compose(f, g) {
  return function(x) {
    return f(g(x));
  };
}

// Define functions for squaring and incrementing
const square = x => x * x;
const increment = x => x + 1;

// Create a composed function
const squareThenIncrement = compose(increment, square);

// Using the composed function
console.log(squareThenIncrement(4)); // Output: 17

/*
Explanation: compose(f, g) creates a new function that applies g and then f.
Output: 17
*/

// Example 5: Function Memoization

// Function to create a memoized version of a function
function memoize(fn) {
  const cache = {};
  return function(x) {
    if (cache[x]) {
      return cache[x];
    }
    const result = fn(x);
    cache[x] = result;
    return result;
  };
}

// Define a slow function
const slowFunction = x => {
  for (let i = 0; i < 1e6; i++); // Simulate a slow operation
  return x * 2;
};

// Create a memoized version
const fastFunction = memoize(slowFunction);

// Using the memoized function
console.log(fastFunction(5)); // Output: 10 (calculated)
console.log(fastFunction(5)); // Output: 10 (cached)

/*
Explanation: memoize(fn) caches results of fn to optimize repeated calls.
Output: 10
        10
*/

Closures

Closures allow functions to retain access to variables from their lexical scope even after the outer function has finished executing.

// Example 1: Basic Closure

// Function to create a counter
function makeCounter() {
  let count = 0; // Private variable
  return function() {
    count += 1;
    return count;
  };
}

// Create a counter instance
const counter = makeCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

/*
Explanation: makeCounter() returns a function that maintains access to count.
Output: 1
        2
*/

// Example 2: Closure with Parameter
// Function to create a multiplier
function createMultiplier(factor) {
  return function(num) {
    return num * factor;
  };
}

// Create multiplier functions
const double = createMultiplier(2);
const triple = createMultiplier(3);

// Applying the multiplier functions
console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

/*
Explanation: createMultiplier(factor) creates a function that multiplies a number by factor.
Output: 10
        15
*/

// Example 3: Closure with Private Data

// Function to create a private bank account
function createBankAccount(initialBalance) {
  let balance = initialBalance; // Private variable
  return {
    deposit(amount) {
      balance += amount;
    },
    withdraw(amount) {
      balance -= amount;
    },
    getBalance() {
      return balance;
    }
  };
}

// Create a bank account instance
const account = createBankAccount(100);
account.deposit(50);
account.withdraw(30);
console.log(account.getBalance()); // Output: 120

/*
Explanation: createBankAccount(initialBalance) uses closures to manage balance.
Output: 120
*/

// Example 4: Closure for Event Handling

// Function to create a button click handler
function createClickHandler(buttonId) {
  let clickCount = 0;
  return function() {
    clickCount += 1;
    console.log(`Button ${buttonId} clicked ${clickCount} times`);
  };
}

// Create click handlers
const button1Handler = createClickHandler(1);
const button2Handler = createClickHandler(2);

// Simulate button clicks
button1Handler(); // Output: Button 1 clicked 1 times
button1Handler(); // Output: Button 1 clicked 2 times
button2Handler(); // Output: Button 2 clicked 1 times

/*
Explanation: createClickHandler(buttonId) uses closures to track click counts for each button.
Output: Button 1 clicked 1 times
        Button 1 clicked 2 times
        Button 2 clicked 1 times
*/

// Example 5: Closure with Iterators

// Function to create a number iterator
function createIterator(start = 0) {
  let current = start;
  return function() {
    current += 1;
    return current;
  };
}

// Create an iterator
const iterator = createIterator(10);
console.log(iterator()); // Output: 11
console.log(iterator()); // Output: 12

/*
Explanation: createIterator(start) creates a function that iterates from start.
Output: 11
        12
*/

Asynchronous Functions

Asynchronous functions enable handling operations that take time to complete, such as network requests, without blocking the execution of other code.

// Example 1: Basic Asynchronous Function

// Asynchronous function that simulates a delay
async function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Using the asynchronous function
async function main() {
  console.log("Starting...");
  await delay(2000); // Wait for 2 seconds
  console.log("Done!");
}

main();

/*
Explanation:
1. async function delay(ms) returns a promise that resolves after ms milliseconds.
2. await delay(2000) pauses execution for 2 seconds.
Output: Starting...
        (Delay of 2 seconds)
        Done!
*/

// Example 2: Asynchronous Function with Return Value

// Asynchronous function that returns data
async function fetchData() {
  // Simulate fetching data
  return "Data fetched!";
}

// Using the asynchronous function
async function main() {
  const data = await fetchData();
  console.log(data);
}

main();
/*
Explanation:
1. async function fetchData() returns data after completion.
2. await fetchData() retrieves the result.
Output: Data fetched!
*/

// Example 3: Handling Errors in Asynchronous Functions
// Asynchronous function with error handling
async function fetchDataWithError() {
  throw new Error("Something went wrong!");
}

// Using the asynchronous function
async function main() {
  try {
    await fetchDataWithError();
  } catch (error) {
    console.log("Error:", error.message);
  }
}

main();
/*
Explanation:
1. fetchDataWithError() throws an error.
2. try...catch handles errors during await.
Output: Error: Something went wrong!
*/

// Example 4: Parallel Execution with Promise.all

// Asynchronous functions
async function fetchData1() {
  return "Data 1";
}

async function fetchData2() {
  return "Data 2";
}

// Using Promise.all to run in parallel
async function main() {
  const [data1, data2] = await Promise.all([fetchData1(), fetchData2()]);
  console.log(data1, data2);
}

main();
/*
Explanation:
1. Promise.all([fetchData1(), fetchData2()]) runs both functions in parallel and waits for both to complete.
Output: Data 1 Data 2
*/

// Example 5: Using Async/Await with Fetch API

// Asynchronous function to fetch data from API
async function fetchFromAPI(url) {
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

// Using the asynchronous function
async function main() {
  const data = await fetchFromAPI('https://jsonplaceholder.typicode.com/posts');
  console.log(data);
}

main();

/*
Explanation:
1. await fetch(url) retrieves the response from the API.
2. await response.json() parses the response as JSON.
*/
  1. JSONPlaceholder:
  2. Random User Generator:
    • Description: A free API for generating random user data.
    • Endpoint: https://randomuser.me/api/
    • Example: https://randomuser.me/api/
  3. OpenWeatherMap:
    • Description: A free API to get weather data.
    • Endpoint: https://api.openweathermap.org/data/2.5/weather?q={city name}&appid={API key}
    • Example: https://openweathermap.org/current
  4. The Dog API:
  5. The Cat API:
  6. Bored API:
  7. Advice Slip JSON API:

These APIs provide various types of data and can be useful for testing, learning, or just for fun.