Async Await

Callback, Promise, Async/Await

function f1(callback) {
 console.log('f1');
 callback();
}

function f2(callback) {
 console.log('f2');
 callback();
}

function f3() {
 console.log('f3');
}

// Invoke
f1(function() {
 f2(function() {
f3();
 });
});

• A callback is simply a function that is passed as an argument to another function, and it is executed after the main function has finished executing. This is one of the simplest ways to handle asynchronous code, though it can lead to “callback hell” if not managed properly.

• In this code, f1, f2, and f3 are executed one after another. After f1 finishes, it calls the callback, which is f2. After f2 finishes, it calls the callback, which is f3. This ensures that each function runs in sequence, even though they’re asynchronous in nature.

function f1() {
 return new Promise((resolve) => {
 console.log('f1');
 resolve();
 });
}

function f2() {
 return new Promise((resolve) => {
 console.log('f2');
 resolve();
 });
}

function f3() {
 return new Promise((resolve) => {
 console.log('f3');
 resolve();
 });
}

// Invoke (sequentially)
f1()
 .then(f2)
 .then(f3);

• A Promise in JavaScript is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises provide a cleaner way to handle asynchronous operations compared to callbacks.

• Each function (f1, f2, f3) returns a promise. The .then() method is used to chain these promises together. When f1 finishes, it resolves its promise, and f2 is then executed, followed by f3.

• Promises avoid the “callback hell” problem by flattening the chain of functions.

function f1() {
 return new Promise((resolve) => {
 console.log('f1');
 resolve();
 });
}

function f2() {
 return new Promise((resolve) => {
 console.log('f2');
 resolve();
 });
}

function f3() {
 return new Promise((resolve) => {
 console.log('f3');
 resolve();
 });
}

// Invoke (concurrently)
Promise.all([f1(), f2(), f3()]);

Theory/Concept:

• Promise.all is a method that takes an array of promises and returns a single promise that resolves when all of the promises in the array have resolved. It allows you to run multiple promises in parallel and wait for all of them to complete before proceeding.

Explanation:

• In this example, f1, f2, and f3 are all executed simultaneously (or as close to it as possible). The Promise.all method waits for all of these promises to resolve before it proceeds. The order of execution might not be guaranteed, but since these functions are synchronous (just logging), they’ll likely log in the order they are written.

function f1() {
 return new Promise((resolve) => {
 setTimeout(() => {
 console.log('f1');
 resolve('f1 done');
 }, 3000); // f1 will resolve after 3 seconds
 });
}

function f2() {
 return new Promise((resolve) => {
 setTimeout(() => {
 console.log('f2');
 resolve('f2 done');
 }, 2000); // f2 will resolve after 2 seconds
 });
}

function f3() {
 return new Promise((resolve) => {
 setTimeout(() => {
 console.log('f3');
 resolve('f3 done');
 }, 1000); // f3 will resolve after 1 second
 });
}

// Invoke (race)
Promise.race([f1(), f2(), f3()])
 .then((result) => {
 console.log('Race finished with:', result);
 })
 .catch((error) => {
 console.error('Race error:', error);
 });

Theory/Concept:

• Promise.race is a method that takes an array of promises and returns a single promise that resolves or rejects as soon as the first promise in the array resolves or rejects. Essentially, it’s a race between the promises, and whichever promise settles first (either resolves or rejects) determines the outcome of the Promise.race.

Explanation:

• In this example, we have three functions (f1, f2, f3) that each return a promise, but they resolve after different amounts of time (3 seconds, 2 seconds, and 1 second, respectively).

• When Promise.race is called, it waits for the first promise to settle (either resolve or reject). In this case, f3 will resolve first (after 1 second), so the promise returned by Promise.race will resolve with the value ‘f3 done’, and the other promises (f1 and f2) will not affect the result.

• The then block will execute, logging “Race finished with: f3 done”.

Why Use Promise.race?

• Promise.race is particularly useful when you want to take action based on whichever asynchronous operation completes first. For example, you might use it to implement a timeout for an operation: if the operation takes too long, you could resolve a timeout promise first, thereby canceling the original operation.

In Summary:

• Promise.race allows you to run multiple promises in parallel and returns the result of the first promise that settles (resolves or rejects). It’s like a race where only the first to cross the finish line matters, making it useful for scenarios where the fastest response is needed.

function f1() {
 return new Promise((resolve) => {
 console.log('f1');
 resolve();
 });
}

function f2() {
 return new Promise((resolve) => {
 console.log('f2');
 resolve();
 });
}

function f3() {
 return new Promise((resolve) => {
 console.log('f3');
 resolve();
 });
}

// Invoke (concurrently)
Promise.allSettled([f1(), f2(), f3()])
 .then((results) => {
 results.forEach((result) => console.log(result.status));
 });

Theory/Concept:

• Promise.allSettled is similar to Promise.all, but it waits for all promises to settle (either resolved or rejected). This is useful when you want to know the outcome of each promise, regardless of whether it succeeded or failed.

Explanation:

• Here, f1, f2, and f3 run concurrently. After all promises have either resolved or rejected, the then block will execute, logging the status of each promise (which will be “fulfilled” in this case because the functions don’t reject).

async function f1() {
 console.log('f1');
}

async function f2() {
 console.log('f2');
}

async function f3() {
 console.log('f3');
}

// Invoke (sequentially)
async function invoke() {
 await f1();
 await f2();
 await f3();
}

invoke();

Theory/Concept:

• async/await is built on top of promises and provides a more synchronous-like syntax for handling asynchronous code. It allows you to write asynchronous code that looks like synchronous code, making it easier to read and understand.

Explanation:

• The async keyword is added to a function to make it return a promise. The await keyword is used to pause the execution of the async function until the promise resolves. This allows f1, f2, and f3 to execute one after the other in a very readable manner.

In Summary:

• Callbacks: Basic way of chaining asynchronous code, but can lead to deeply nested code (callback hell).

• Promises: A cleaner, more manageable way to handle asynchronous code, with methods like .then() and .catch().

• Promise.all: Run multiple promises concurrently and wait for all to resolve.

• Promise.allSettled: Run multiple promises concurrently and wait for all to settle (resolve or reject).

• Async/Await: Syntactic sugar over promises, allowing you to write asynchronous code in a more synchronous style, making it easier to read and debug.

Callback, Promise, Async/Await

function f1(callback) {
    console.log("Hello!");
    if (callback) callback();
  }
  
  function f2(callback) {
    setTimeout(() => {
      console.log("Goodbye!");
      if (callback) callback();
    }, 1000);
    f1();
  }
  
  function f3() {
    f2(() => {
      let x = false;
      if (x) {
        console.log("Thank you!");
      } else {
        console.log("Error");
      }
      console.log("See you again!");
    });
  }
  
  f3();

function f1() {
    return new Promise((resolve) => {
      console.log("Hello!");
      resolve();
    });
  }
  
  function f2() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("Goodbye!");
        resolve();
      }, 1000);
      f1();
    });
  }
  
  function f3() {
    f2()
      .then(() => {
        return new Promise((resolve, reject) => {
          let x = false;
          if (x) {
            resolve("Thank you!");
          } else {
            reject("Error");
          }
        });
      })
      .then((message) => {
        console.log(message);
      })
      .catch((error) => {
        console.log(error);
      })
      .finally(() => {
        console.log("See you again!");
      });
  }
  
  f3();

function f1() {
    return new Promise((resolve) => {
      console.log("Hello!");
      resolve();
    });
  }
  
  function f2() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("Goodbye!");
        resolve();
      }, 1000);
    });
  }
  
  function f3() {
    Promise.all([f1(), f2()])
      .then(() => {
        return new Promise((resolve, reject) => {
          let x = false;
          if (x) {
            resolve("Thank you!");
          } else {
            reject("Error");
          }
        });
      })
      .then((message) => {
        console.log(message);
      })
      .catch((error) => {
        console.log(error);
      })
      .finally(() => {
        console.log("See you again!");
      });
  }
  
  f3();

function f1() {
    return new Promise((resolve) => {
      console.log("Hello!");
      resolve();
    });
  }
  
  function f2() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("Goodbye!");
        resolve();
      }, 1000);
    });
  }
  
  function f3() {
    Promise.race([f1(), f2()])
      .then(() => {
        return new Promise((resolve, reject) => {
          let x = false;
          if (x) {
            resolve("Thank you!");
          } else {
            reject("Error");
          }
        });
      })
      .then((message) => {
        console.log(message);
      })
      .catch((error) => {
        console.log(error);
      })
      .finally(() => {
        console.log("See you again!");
      });
  }
  
  f3();

function f1() {
    return new Promise((resolve) => {
      console.log("Hello!");
      resolve();
    });
  }
  
  function f2() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("Goodbye!");
        resolve();
      }, 1000);
    });
  }
  
  function f3() {
    Promise.allSettled([f1(), f2()])
      .then(() => {
        return new Promise((resolve, reject) => {
          let x = false;
          if (x) {
            resolve("Thank you!");
          } else {
            reject("Error");
          }
        });
      })
      .then((message) => {
        console.log(message);
      })
      .catch((error) => {
        console.log(error);
      })
      .finally(() => {
        console.log("See you again!");
      });
  }
  
  f3();

function f1() {
    return new Promise((resolve) => {
      console.log("Hello!");
      resolve();
    });
  }
  
  function f2() {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("Goodbye!");
        resolve();
      }, 1000);
    });
  }
  
  async function f3() {
    await f1();
    await f2();
  
    try {
      let x = false;
      if (x) {
        console.log("Thank you!");
      } else {
        throw new Error("Error");
      }
    } catch (error) {
      console.log(error.message);
    } finally {
      console.log("See you again!");
    }
  }
  
  f3();

function f1() {
    console.log("Hello!");
  }
  function f2() {
      setTimeout((f2)=>{console.log("Goodbye!");}, 1000)
      f1();
    };
  function f3() {
    f2();
    new Promise((resolve, reject)=>{
      let x = false;
      if(x){
          resolve("Thank you!")
      }else {
          reject("Error")
      }
    })
    .then((message) => {console.log(message)})
    .catch((error) => {console.log(error)})
    .finally(()=> {console.log("See you again!")})
  }
  f3();

This code involves a mix of synchronous and asynchronous behavior:

• f1() simply logs “Hello!”.

• f2() does two things:

• It schedules a message “Goodbye!” to be logged after 1 second using setTimeout.

• It immediately calls f1(), which logs “Hello!”.

• f3() calls f2() and then creates a promise that:

• Resolves with “Thank you!” if x is true.

• Rejects with “Error” if x is false.

• Regardless of the outcome, logs “See you again!” in the finally block.


When f3() is invoked:

1. “Hello!” is logged immediately by f1() (called from f2()).

2. “Goodbye!” is logged after 1 second (from setTimeout in f2()).

3. The promise in f3() rejects with “Error”, so “Error” is logged, followed by “See you again!”.

1. Callback Version

You can add callbacks to handle asynchronous behavior, but note that the original f3 already handles the promise using .then() and .catch(). Here’s how you could add callbacks:

Summary of Combinations:

1. Callback Version: Manually chain the execution using callbacks.

2. Promise Version: Chain the execution using .then() and .catch().

3. Promise.all Version: Wait for multiple promises to resolve and then proceed.

4. Promise.allSettled Version: Wait for all promises to settle (resolve or reject) and then proceed.

5. Promise.race Version: Proceed as soon as the first promise settles (resolve or reject).

6. Async/Await Version: Use async/await to write asynchronous code in a more synchronous style.

Each approach gives you a different level of control over how the functions execute and handle asynchronous behavior.

Callback, Promise, Async/Await

function fetchData(url, callback) {
    fetch(url)
      .then(response => response.json())
      .then(data => callback(null, data))
      .catch(error => callback(error, null));
  }
  
  function handleData(error, data) {
    if (error) {
      console.error("An error occurred:", error);
    } else {
      console.log("Data received:", data);
    }
  }
  
  // Example URL (replace with any valid API endpoint)
  const url = 'https://jsonplaceholder.org/users';
  
  // Invoke
  fetchData(url, handleData);

// Explanation:

// • fetchData takes a url and a callback function.

// • Inside fetchData, fetch(url) is called to retrieve data.

// • If the data is successfully retrieved, it calls callback(null, data).

// • If there’s an error, it calls callback(error, null).

function fetchData(url) {
    return fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      });
  }
  
  // Example URL
  const url = 'https://jsonplaceholder.org/users';
  
  fetchData(url)
    .then(data => {
      console.log("Data received:", data);
    })
    .catch(error => {
      console.error("An error occurred:", error);
    });

// Explanation:

// • fetchData returns a promise that resolves with the JSON data if the request is successful.

// • .then() is used to handle the successful data retrieval, and .catch() handles any errors.

const urls = [
    'https://jsonplaceholder.typicode.com/posts/1',
    'https://jsonplaceholder.typicode.com/posts/2',
    'https://jsonplaceholder.typicode.com/posts/3'
  ];
  
  function fetchData(url) {
    return fetch(url).then(response => response.json());
  }
  
  Promise.all(urls.map(url => fetchData(url)))
    .then(dataArray => {
      console.log("All data received:", dataArray);
    })
    .catch(error => {
      console.error("An error occurred:", error);
    });

// Explanation:

// • Promise.all takes an array of promises and resolves when all of them are resolved.

// • This is useful when you need to fetch multiple resources and wait for all of them to be available.

const urls = [
    'https://jsonplaceholder.typicode.com/posts/1',
    'https://jsonplaceholder.typicode.com/posts/2'
  ];
  
  function fetchData(url) {
    return fetch(url).then(response => response.json());
  }
  
  Promise.race(urls.map(url => fetchData(url)))
    .then(data => {
      console.log("First data received:", data);
    })
    .catch(error => {
      console.error("An error occurred:", error);
    });

// Explanation:

// • Promise.race is useful when you want the result of whichever fetch completes first.

// • This could be used in scenarios where you want to use the fastest response among multiple possible sources.

const urls = [
    'https://jsonplaceholder.typicode.com/posts/1',
    'https://jsonplaceholder.typicode.com/invalid-url', // Invalid URL to cause an error
    'https://jsonplaceholder.typicode.com/posts/3'
  ];
  
  function fetchData(url) {
    return fetch(url).then(response => response.json());
  }
  
  Promise.allSettled(urls.map(url => fetchData(url)))
    .then(results => {
      results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          console.log(`Data from URL ${index + 1}:`, result.value);
        } else {
          console.error(`Error with URL ${index + 1}:`, result.reason);
        }
      });
    });

// Explanation:

// • Promise.allSettled returns a promise that resolves after all of the given promises have either resolved or rejected.

// • It provides the status of each promise, which you can handle accordingly.

async function fetchData(url) {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    return data;
  }
  
  async function fetchMultipleData() {
    try {
      const url = 'https://jsonplaceholder.typicode.com/posts/1';
      const data = await fetchData(url);
      console.log("Data received:", data);
    } catch (error) {
      console.error("An error occurred:", error);
    }
  }
  
  fetchMultipleData();

// Explanation:

// • The async function fetchData waits for the fetch() promise to resolve and then for the JSON data to be parsed.

// • fetchMultipleData demonstrates how to call fetchData using await, handling errors with try/catch.

• Callback: Functions passed as arguments to handle asynchronous operations, but can lead to “callback hell.”

• Promise: A more modern way to handle asynchronous operations with .then() and .catch().

• Promise.all: Wait for multiple promises to resolve; great for fetching multiple resources simultaneously.

• Promise.allSettled: Wait for all promises to settle, whether they resolve or reject.

• Promise.race: Returns the result of the first promise that settles; useful for picking the fastest operation.

• Async/Await: Syntactic sugar over promises that allows you to write asynchronous code in a more synchronous style.

Each of these examples uses fetch() to demonstrate how to handle asynchronous operations in different ways, helping you understand the theory and practical application of each method.

function downloadFile(url, callback) {
  console.log(`Starting download from ${url}...`);
  setTimeout(() => {
    console.log(`Finished downloading from ${url}`);
    callback();
  }, 2000); // Simulates a 2-second download time
}

function processFile() {
  console.log('Processing the downloaded file...');
}

// Usage
downloadFile('http://example.com/file', processFile);

// Output:
// Starting download from http://example.com/file...
// Finished downloading from http://example.com/file
// Processing the downloaded file...

function downloadFile(url) {
return new Promise((resolve, reject) => {
console.log(`Starting download from ${url}...`);
setTimeout(() => {
console.log(`Finished downloading from ${url}`);
resolve('Download complete');
}, 2000); // Simulates a 2-second download time
});
}

// Usage
downloadFile('http://example.com/file')
.then((message) => {
console.log(message); // Logs "Download complete"
console.log('Processing the downloaded file...');
})
.catch((error) => {
console.error('Download failed:', error);
});

function downloadFile(url) {
  return new Promise((resolve, reject) => {
    console.log(`Starting download from ${url}...`);
    setTimeout(() => {
      console.log(`Finished downloading from ${url}`);
      resolve('Download complete');
    }, 2000); // Simulates a 2-second download time
  });
}

async function downloadAndProcess() {
  try {
    const message = await downloadFile('http://example.com/file');
    console.log(message); // Logs "Download complete"
    console.log('Processing the downloaded file...');
  } catch (error) {
    console.error('Download failed:', error);
  }
}

// Usage
downloadAndProcess();

function downloadFile(url, time) {
return new Promise((resolve, reject) => {
console.log(`Starting download from ${url}...`);
setTimeout(() => {
if (Math.random() > 0.2) {
console.log(`Finished downloading from ${url}`);
resolve(`${url} download complete`);
} else {
reject(`${url} download failed`);
}
}, time);
});
}

const download1 = downloadFile('http://example.com/file1', 1000);
const download2 = downloadFile('http://example.com/file2', 2000);
const download3 = downloadFile('http://example.com/file3', 3000);

Promise.all([download1, download2, download3])
.then((messages) => {
console.log('All downloads complete:', messages);
})
.catch((error) => {
console.error('One or more downloads failed:', error);
});

function downloadFile(url, time) {
  return new Promise((resolve, reject) => {
    console.log(`Starting download from ${url}...`);
    setTimeout(() => {
      if (Math.random() > 0.2) {
        console.log(`Finished downloading from ${url}`);
        resolve(`${url} download complete`);
      } else {
        reject(`${url} download failed`);
      }
    }, time);
  });
}

const download1 = downloadFile('http://example.com/file1', 1000);
const download2 = downloadFile('http://example.com/file2', 2000);
const download3 = downloadFile('http://example.com/file3', 3000);

Promise.allSettled([download1, download2, download3])
  .then((results) => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`Download ${index + 1} succeeded:`, result.value);
      } else {
        console.log(`Download ${index + 1} failed:`, result.reason);
      }
    });
  });