Promises in JS are essentially empty objects that are filled asynchronously. Before promises, developers used callback functions, but they were often a nightmare to work with.
Before Promises
Before promises, we used to depend on callback functions which resulted in:
- Callback Hell (Pyramid of Doom)
- Inversion of Control
Overcoming Inversion of Control
Inversion of control is overcome by using Promises.
- A promise is an object that represents the eventual completion or failure of an asynchronous operation.
- A promise has 3 states: pending | fulfilled | rejected.
- As soon as a promise is fulfilled/rejected, it updates the empty object (which is assigned undefined in the pending state).
- A promise resolves only once and it is immutable.
- Using
.then()we can control when we call the callback function.
A Promise breakdown:
Pending
Initial state, neither fulfilled nor rejected.
Fulfilled
Meaning that the operation was completed successfully.
Rejected
Meaning that the operation failed.
Promise Chaining
To avoid Callback Hell (Pyramid of Doom), we use promise chaining. This way our code expands
vertically instead of horizontally. Chaining is done using .then().
Common Mistake
A very common mistake that developers do is not returning a value during chaining of promises.
Always remember to return a value. This returned value will be used by the next .then().
I've been writing JS for a year, but I only fully appreciated the beauty of this implementation today.
One interesting thing I learned today (TIL) is why string literals are called literals. I never thought about it this way, but they can't "literally" print an object if you try to interpolate it directly in some contexts.
The string literal `data: ${data}` won't print the actual object structure; it just prints "data: [object Object]". It's a subtle but important distinction when debugging!