DEV Community

Zeppa
Zeppa

Posted on

Promises, Promises . . .

What is a Promise?

In JavaScript, a Promise represents an object whose result is initially unknown. It is used to more efficiently synchronize program execution. Once execution is complete, the value of the object is updated.

Many JavaScript libraries are based on the implementation of Promises, such as jQuery's Deferred Object and NodeJS's node-promise.

Why is a Promise Special?

JavaScript is single-threaded programming language, meaning that only one command is executed at a time. To solve this limitation, JavaScript introduced Promises in its ES6 release. It provides a way to run code asynchronously.

Prior to promises, only callbacks were used to write asynchronous code. So, what is a callback? A callback is a function that is passed into another function as an argument. The callback function is then invoked inside the containing function to complete some action. As the level of code complexity rises, so does the amount of nested or cascading callbacks to create asynchronous code. To the point, that it has become known as "Callback Hell".

The Promise Constructor

Promises expect a value eventually. As a result, promises have their own constructor. It takes in a function which is known as an executor. When a new promise is created, the function or executor runs automatically.

The following is a simple promise constructor:

let promise = new Promise(function(resolve, reject) {
  // Code that will produce a result
});

The parameters of the executor function are resolve and reject; both represent callbacks provided by JavaScript. The appropriate callback will then be execute when a result is obtained.

let promise = new Promise(function(resolve, reject) {
  if (error) {
    reject();
  }
  resolve();
});

The promise object returned by the constructor has two properties: state and result. Both change during the function's execution. State is initially pending, and then updates to fulfilled or rejected depending on whether resolve or reject is called, respectively. Result is initially undefined. If the operation has successfully completed, resolve will be called with a resulting value. Otherwise, an error occurred, so reject will be called with an error.

Here comes .then, .catch, and .finally

Since the properties of promises cannot be accessed directly, .then, .catch, and .finally help control how and where the code is handled.

The .then method takes in two arguments, which are both functions. The first function runs when the promise is resolved and receives a result; the second function runs when the promise is rejected and receives an error. However, it is not necessary to include both functions. If an error is not of importance, only provide the first or result function. If the error is of more interest, provide null as the first argument and provide the error-handling function as the second argument.

promise.then(
  result => {
    // Code that will run if fulfilled
  }, 
  error => {
    // Code that will run if rejected
  }
) 

Instead of providing null as the first argument along with the error-handling function in a .then method, .catch can also be used.

The following illustrates the difference of using .then and .catch to only handle error:

promise.then(null, error => {
    // Code that will run if rejected
  }
) 

promise.catch(function () {
  // Will execute the promise's reject code
});

There are also situations where the same function would be run whether it was resolved or rejected. Instead of providing the same function for both the resolve and reject of a .then method, a .finally method can be used. The .finally method will always run no matter what as long as the promise has been settled.

promise.then(
  result => {
    // Same code as error function
  }, 
  error => {
    // Same code as result function
  }
) 

promise.finally(() => {
  // Code that will run if fulfilled or rejected
});  

While a promise is pending, the .then, .catch, and .finally methods wait for its completion. Once its settled, they execute immediately.

Promise Chaining

Chaining results in running code in serial, just like synchronous code. It is similar to when built-in methods are chained together, or when chaining together higher-order functions.

promise
  .then(resolveFunction)
  .catch(errorFunction)
  .finally(noMatterWhatFunction);

Since .then always returns a new promise, it is possible to chain several together in order to provide the control necessary over the codes execution. This helps mimic normal synchronous code.

promise
  .then(resolveFunction1)
  .then(resolveFunction2)
  .then(resolveFunction3)
  .catch(errorFunction)
  .finally(noMatterWhatFunction);

Conclusion

Asynchronous code allows execution of other code while it waits for the unknown. Even though both callbacks and promises provide a way to run code asynchronously, promises result in cleaner code, which is easier to debug. Promises help save a place for the unknown, and they are always waiting for the resulting value no matter where it occurs in the code.

Top comments (0)