3

Why JavaScript Promises are awesome

 2 years ago
source link: https://dev.to/methmi/why-javascript-promises-are-awesome-4obk
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

What is a Promise?

A promise, in computer science, is basically a concept that handles a value that is to be produced in the future, after an asynchronous operation completes successfully or if it does not, gracefully handles a failure.

Too much jargon?

Here’s a high level picture -

You may lend something to a friend and trust them enough to know that you won’t have to stop whatever you were doing and wait for them to return it. You know that they will return it eventually, so, you’ll be able to go one with your life until the item (or an excuse 😅) is returned.
Although you will act on the eventual outcome of the promise that the friend gave you, you will save that for the time the promise is actually fulfilled.

This was the concept in mind when the term Promise (in computer science) was first coined in 1976.

Promise concepts were popularized in the JavaScript world by the jQuery Deferred Objects, that were similar in concept to the Promise, but differed from the current specification of Promises in ECMAScript 2015.

After their official acceptance into the ECMAScript 2015 spec, they are now implemented in all the latest browsers and Node, and are here to cleanup the potential muddle in callbacks and simplify the process.

What about Callback functions?

Callbacks also produce values after an asynchronous operation but executing one asynchronous operation after the other requires callbacks to be called inside another callback, as shown below.

The callbacks being nested deeper and deeper cause the code to expand horizontally as well, leading to an error-prone and confusing code block understandably known as ‘Callback Hell’.

Promises are used to bypass this.

Using Promises instead

A promise can be created with its constructor:

let promise = new Promise(function(resolve, reject) {
   // Your application logic goes here
});

The function taken into the constructor is the executor function and takes in two arguments: Resolved and Rejected.

The code inside this function is the logic to be executed each time a new Promise is created.

The Promise returned from the constructor should be able to inform the executor function, handling async (asynchronous) operations, when execution has started, completed or returned an error.

Parts of a promise

A promise has a STATE and a RESULT

A promise has 3 states:

  • Pending

  • Fulfilled

  • Rejected

Pending promises in JavaScript, much like in the real world, is a promise that has been executed but not yet completed and can therefore move to the ‘Fulfilled’ or ‘Rejected’ state.

Fulfilled promises are resolved or completed, indicating a successful outcome.

Rejected promises indicate an unsuccessful outcome due to an error or a timeout.

And, promises that are either Fulfilled or Rejected are called Settled.

The result property of a promise can hold the values:

  • undefined : When the state is pending

  • value : When resolve(value) is called

  • error : When reject(error) is called

Resolving and Rejecting

The promise can be resolved with resolve() and placed in the fulfilled state, or rejected with an error as reject(new Error('Something went wrong')) in and placed in rejected state.

let myPromise = new Promise(function(resolve, reject) {
   resolve("This will be resolved"); // EXECUTED
   reject(new Error('Something went wrong')); // ignored
   resolve("Resolved again?"); // ignored
});

In this example, reject() and resolve() are being called again after the promise has been fulfilled.

But, once the state has changed, any further calls to reject and resolve will be ignored.

Handling a promise

The handler functions .then(), .catch(), .finally() allow functions consuming the promise to be in sync with the executor function when a promise is fulfilled/rejected.

Using .then()

This is called to handle a promise’s rejection or fulfillment, and can therefore accept up to two functions as arguments:

myPromise.then(
  (result) => { // a successful outcome, promise fulfilled
      console.log(result);
   },
   (error) => { // promise rejected
      console.log(error);
   }
);

Or if only one of the two outcomes are needed:

// logs SUCCESFUL outcomes only
myPromise.then(
   (result) => {
      console.log(result);
   }
);
// logs ERROR outcomes only
myPromise.then(
   null,
   (error) => {
      console.log(error);
   }
);

Using .catch()

Leaving one argument null in .then() to handle errors is not so useful. This is when .catch() used.

In the following example getPromise() is a user implemented function that accepts a URL and returns a promise stating its outcome.

let urlPromise = getPromise(BAD_URL);
   const consumerFunc = () => {
   promise.catch(error => console.log(error));
}
consumerFunc ();

Using .finally()

This handler cleans up after an execution (for example, close a live connection) and will run regardless of whether a promise is rejected or fulfilled.

Here is .finally() being used with the other handlers:

let isLoading = true;
isLoading && console.log('Loading...');
//Promise with outcome of URL
promise = getPromise(A_URL);
promise.finally(() => {
   isLoading = false;
   console.log(`Promise settled and loading is ${isLoading}`);
}).then((result) => {
   console.log({result});
}).catch((error) => {
   console.log(error)
});
  • .finally() will set isLoading to false.

  • If the URL is valid, .then() is executed and result is logged.

  • If the URL is invalid, error is caught and .catch() is executed and error is logged.

  • .finally() will be called regardless of a resolved or rejected promise.

Chaining Promises

aPromise.then() always returns a promise this lets us call .then() on the new promise leading to a chain of .then() methods passing down a single promise.

A .then() can return a new promise, or throw an error too. The following example demonstrates this:

let myPromise = getPromise(URL_RETURNING_MULTIPLE_ITEMS);
promise.then(result => {
   // extracts URL of oneItem
   let oneItem = JSON.parse(result).results[0].url;
   return oneItem; // returns the URL
}).then(oneItemURL => {
   console.log(oneItemURL);
   return getPromise(oneItemURL); // new promise returned
}).then(itemData => {
   console.log(JSON.parse(itemData));
}).catch(error => {
   console.log(‘Error caught. In the Catch block’, error);
});

Where the new promise is created, the URL from the previous .then() is passed into getPromise() which returns the new promise that is then resolved sent down the chain where itemData is logged.

If an error is caught, the .catch() in the chain is triggered and the error is handled.

And those were the basics of how Promises can be used for asynchronous functions, resulting in clean, understandable code that’s less prone to errors.

Till next time, happy coding!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK