logo
logo

Callbacks & Promises Cheatsheet

What are Callbacks?

In Javascript, a callback is a function that is passed as an argument to another function, and is then executed when the main function completes its task. Callback functions are a fundamental concept in Javascript, and are used extensively in asynchronous programming.

How to use Callbacks to write Asynchronous code

Callbacks are commonly used in asynchronous programming, where a task is executed in the background, and the result is returned later. When writing asynchronous code, the main program flow continues executing while the asynchronous task is being executed in the background. Once the asynchronous task is complete, the callback function is called to handle the result.

Here's an example of how to use a callback to write asynchronous code in Javascript:

function doAsyncTask(callback) {
    setTimeout(function() {
        // Simulate an asynchronous task
        callback('Done!');
    }, 1000);
}

doAsyncTask(function(result) {
    console.log(result); // Prints 'Done!'
});
In the example above, the
doAsyncTask
function takes a callback function as an argument. The
setTimeout
function is used to simulate an asynchronous task that takes one second to complete. Once the task is complete, the callback function is called with the result.

Callback Hell and How to Avoid It

Callback hell is a common problem that arises when a series of asynchronous tasks are executed one after the other, with each task relying on the result of the previous task. This can result in deeply nested and unreadable code. Here's an example of callback hell:

function step1(callback) {
    setTimeout(function() {
        console.log('Step 1');
        callback();
    }, 1000);
}

function step2(callback) {
    setTimeout(function() {
        console.log('Step 2');
        callback();
    }, 1000);
}

function step3(callback) {
    setTimeout(function() {
        console.log('Step 3');
        callback();
    }, 1000);
}

step1(function() {
    step2(function() {
        step3(function() {
            console.log('Done!');
        });
    });
});

To avoid callback hell, there are a few techniques that can be used, such as using named functions, breaking down code into smaller functions, and using control flow libraries such as Async.js.

Introduction to Promises in the Context of Callbacks and ES6

Promises are an improvement over callbacks in handling asynchronous code. Promises were introduced in ES6 and provide a cleaner and more readable way of handling asynchronous code. Promises are objects that represent the eventual completion or failure of an asynchronous operation.

Here's an example of using Promises in the context of callbacks:

function doAsyncTask() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            // Simulate an asynchronous task
            resolve('Done!');
        }, 1000);
    });
}

doAsyncTask().then(function(result) {
    console.log(result); // Prints 'Done!'
}).catch(function(error) {
    console.error(error);
});
In the example above, the
doAsyncTask
function returns a Promise object that represents the completion or failure of the asynchronous task. The
then
function is used to handle the successful completion of the task, and the
catch
function is used to handle any errors that may occur.

Introduction to Promises

Promises provide an alternative way to handle asynchronous operations in JavaScript. Promises are used instead of callbacks to handle asynchronous computations. Promises represent a value that may be available immediately, later, or never.

A promise is in one of three states: pending, fulfilled, or rejected. The pending state is the initial state of a promise, and it can transition to either fulfilled or rejected state.

Creating Promises

To create a new promise, we use the
new Promise()
constructor. We pass a function with two arguments,
resolve
and
reject
, to the constructor. We use the
resolve
function to return a value when the promise is fulfilled and the
reject
function to return an error if the promise is rejected.

Here's an example of creating a promise:

const testPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise resolved");
  }, 1000);
});
In this example, we create a new promise named
testPromise
. The
setTimeout()
function is used to simulate an asynchronous operation that takes 1 second to complete. When the operation is complete, we call the
resolve
function with the string "Promise resolved".

Chaining Promises

We can chain promises together using the
.then()
method. The
.then()
method takes a function that will be executed when the promise is fulfilled. We can chain multiple
.then()
methods together to handle a series of promises.

Here's an example of chaining promises:

testPromise
  .then((message) => {
    console.log(message);
    return "Promise resolved again";
  })
  .then((message) => {
    console.log(message);
  });
In this example, we chain two promises together using the
.then()
method. The first
.then()
method takes a function that will be executed when the promise is fulfilled. In this case, we log the message to the console and return a new message. The second
.then()
method takes a function that will be executed when the second promise is fulfilled. In this case, we log the new message to the console.

Error Handling with Promises

We can handle errors with promises using the
.catch()
method. The
.catch()
method takes a function that will be executed when the promise is rejected.

Here's an example of error handling with promises:

const errorPromise = new Promise((resolve, reject) => {
  if (Math.random() > 0.5) {
    resolve("Promise resolved");
  } else {
    reject("Promise rejected");
  }
});

errorPromise
  .then((message) => {
    console.log(message);
  })
  .catch((error) => {
    console.log(error);
  });
In this example, we create a new promise named
errorPromise
. We use a random number to simulate a promise that is resolved half of the time and rejected the other half of the time. We handle the rejected promise using the
.catch()
method. The function passed to the
.catch()
method is executed when the promise is rejected.

Shorthand for Promises

The syntax for creating and consuming Promises can sometimes be verbose, especially when working with complex async code.

Fortunately, there are some shorthand notations that can simplify the syntax for creating and consuming Promises in JavaScript. Here are some of the most common shorthand techniques for Promises:

Async/await

Async/await is a powerful feature introduced in ES2017 that allows us to write asynchronous code that looks like synchronous code. By using the "async" keyword in front of a function and "await" keyword in front of a Promise, we can avoid the use of .then() and .catch() methods. Here is an example:

async function getData() {
  try {
    const response = await fetch('https://example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

In this example, we use the "await" keyword to wait for the response from the server, and then parse the response into JSON. The try-catch block allows us to handle any errors that may occur during the async operation.

Promise.resolve() and Promise.reject()

The Promise.resolve() and Promise.reject() methods are shorthand notations for creating a Promise that either resolves or rejects immediately. Here is an example:

const successPromise = Promise.resolve('Success!');
const errorPromise = Promise.reject('Error!');

In this example, we create two Promises: "successPromise" that resolves with the string "Success!", and "errorPromise" that rejects with the string "Error!".

Understanding Promises and their Methods

A Promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises are an alternative to callbacks for handling asynchronous operations in JavaScript. With promises, we can create more structured, sequential, and readable code.

There are two main methods for handling the result of a Promise.

  • then()
    method: This method is called after the Promise is resolved and accepts a callback function as an argument that will be called with the resolved value of the Promise.
  • catch()
    method: This method is called if the Promise is rejected and accepts a callback function that will be called with the rejected value of the Promise.

Creating Sequential Promises

Let's start with a simple example to demonstrate how to create sequential Promises in JavaScript. We will create a function that logs a message to the console after a specified amount of time using a Promise.

function logger(message, time) {
  return new Promise((resolve, reject) => {
    if (typeof message !== 'string') {
      reject(new Error('Message should be a string!'));
    }
    setTimeout(() => {
      console.log(message);
      resolve();
    }, time);
  });
}
This function takes two parameters:
message
and
time
. The
message
parameter is the string that will be logged to the console, and the
time
parameter is the time in milliseconds after which the message will be logged.
We can use the
then()
method to create sequential Promises. For example, we can create four Promises that log messages after a specified amount of time:
logger('first', 1000)
  .then(() => {
    return logger('second', 500);
  })
  .then(() => {
    return logger('third', 300);
  })
  .then(() => {
    return logger('fourth', 200);
  })
  .catch((error) => {
    console.error(error);
  });
In this example, the
logger
function returns a Promise, which is then chained with the next Promise using the
then()
method. The Promises are executed sequentially, so each message is logged after the specified amount of time has passed.
If any Promise is rejected, the
catch()
method will be called, and the error will be logged to the console.

Promise.all()

The Promise.all() method is a shorthand notation for running multiple Promises in parallel and waiting for all of them to complete. Here is an example:

const promise1 = fetch('https://example.com/data1');
const promise2 = fetch('https://example.com/data2');
const promise3 = fetch('https://example.com/data3');

Promise.all([promise1, promise2, promise3])
  .then((responses) => {
    const data1 = responses[0].json();
    const data2 = responses[1].json();
    const data3 = responses[2].json();
    console.log(data1, data2, data3);
  })
  .catch((error) => {
    console.error(error);
  });

In this example, we use Promise.all() to fetch three sets of data in parallel. When all the Promises resolve, we extract the JSON data from each response and log it to the console. If any of the Promises reject, we catch the error and log it to the console.

Handling Errors with Promises

In order to handle errors with promises, we use the
catch
method, which takes a function as an argument. This function will be called if the promise is rejected at any point in the chain.

For example:

fetch('https://example.com/api/data')
  .then(response => {
    // handle successful response
  })
  .catch(error => {
    // handle error
  });
In this example, if the fetch request fails for any reason, the
catch
method will be called with the error object as the argument.
It's important to note that
catch
only catches errors that occur in the previous promise in the chain. If there are errors in any subsequent promises, they will need to be handled with their own
catch
methods.
Another important thing to keep in mind when working with promises and error handling is to always return a rejected promise when an error occurs. This is necessary in order to propagate the error down the chain and trigger any subsequent
catch
methods.

For example:

fetch('https://example.com/api/data')
  .then(response => {
    if (response.status === 404) {
      return Promise.reject(new Error('Data not found'));
    }
    return response.json();
  })
  .then(data => {
    // handle successful response
  })
  .catch(error => {
    // handle error
  });
In this example, if the response status is 404, we return a rejected promise with an error message. This will trigger the
catch
method and handle the error appropriately.