A promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Promises provide a cleaner and more flexible way to handle asynchronous operations compared to callbacks. They allow you to chain multiple asynchronous operations together and handle errors more effectively.
The general syntaxt of a promise is as follows:
The Promise constructor takes a function as an argument, which in turn takes two functions: resolve and reject. The resolve function is called when the asynchronous operation is successful, and the reject function is called when the operation fails.
Once the promise is created, you can use the then method to handle the successful completion of the promise and the catch method to handle any errors that occur during the promise.
You can also chain multiple promises together using the then method. This allows you to perform a series of asynchronous operations in sequence.
Let’s refactor the example from the previous section to use promises instead of callbacks. We start with the getId function, which currently uses a callback to handle the asynchronous operation of fetching the student’s ID:
We need to update the getId function to return a promise instead of using a callback.
Notice that we have removed the callbackFunction parameter. Moreover, we have created a new Promise object and returned it from the function. The Promise constructor takes a function as an argument, which in turn takes two functions: resolve and reject.
We call the resolve function when the asynchronous operation is successful and the reject function when the operation fails.
In this case, when the asynchronous operation is successful, we want to resolve the promise with the student’s ID. And when the operation fails, we want to reject the promise with an error. Let’s update the getId function to reflect this:
We can simulate the asynchronous operation using a setTimeout function.
Here is a slightly more concise version of the getId function:
We can now use the getId function with promises to fetch the student’s ID:
The then method is used to handle the successful completion of the promise, and the catch method is used to handle any errors that occur during the promise. Run the code above to see the output in the console.
Then, try changing the success variable to false in the getId function to simulate a failed asynchronous operation. You should see the error message being logged to the console.
Let’s go ahead and refactor the getCourses function to use promises as well. Here is the original version of the getCourses function that uses a callback:
We need to update the getCourses function to return a promise instead of using a callback.
We can now use the getCourses function with promises to fetch the student’s courses:
Notice the first then block where we call the getCourses function after successfully fetching the student’s ID. We return the result of the getCourses function to chain it with the next then block. This is how we can chain multiple promises together using the then method. This allows us to perform a series of asynchronous operations in sequence. Run the code above to see the output in the console.
Let us refactor the getGrades function to use promises as well. Here is the original version of the getGrades function that uses a callback:
We need to update the getGrades function to return a promise instead of using a callback.
We can now use the getGrades function with promises to fetch the student’s grades:
Run the code above to see the output in the console.
So what happened here? The id variable is not defined in the getGrades function because it is out of scope. One way to fix this is to pass the id variable along with the courses variable when resolving the promise in the getCourses function. This way, the id variable will be available in the subsequent then block. Here is the updated code:
Notice how we are now resolving the promise with an object that contains both the student_id and courses. This allows us to access the student_id in the subsequent then block. Let’s update the then block in the main code to reflect this change:
Notice how we are now destructuring the object returned by the getCourses function to access both the id and courses variables. This allows us to pass the id variable to the getGrades function. Run the code above to see the output in the console.
You might eb wondering why we passed an object to the resolve function instead of doing something like resolve(id, courses). The reason is that the resolve function can only accept a single argument. By passing an object, we can include multiple values in a single argument and then destructure them in the then block. This is a common pattern when working with promises.
At this point we have refactored the getId, getCourses, and getGrades functions to use promises instead of callbacks.
Promises provide a cleaner way to chain multiple asynchronous operations together and handle errors more effectively. Promise chaining flattens the nested structure of callback hell. This makes the code more readable and maintainable.
There is more to promises than what we have covered here. Promises also have other methods like all, race, and allSettled that can be used to work with multiple promises simultaneously. These are beyond the scope of this note, but you can explore them further in the MDN Web Docs.