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 setisLoading
tofalse
.If the URL is valid,
.then()
is executed andresult
is logged.If the URL is invalid,
error
is caught and.catch()
is executed anderror
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!
Top comments (12)
To highlight JavaScript syntax, you just write the word JavaScript or js after the opening triple quote:
Thank you so much for that input!
Great article
It really helped me think deeply about promises. A promise is basically an object that represents the result of an asynchronous operation. That result can be your desired value or an error. For example if you are making a fetch request for some posts then the desired result is the array of posts. If something goes wrong on the server then the result is an error telling you what went wrong. Either way you get a result from the promise, a desired value or an error.
Thank you 🙌
Nice article. Thanks for sharing. You helped me clear more points on promises which helps me a lot in my projects.
Thanks, glad it helped you!
It was really useful. Thank’s!
same here,
Great article
Thanks!
Great job on your first article 🎉. You totally should give observables (and rxjs) a look though.
Will do 🙌 Thanks for the suggestion!