JAVASCRIPT

Understanding Promises in JavaScript

Deep-dive into the world of JavaScript Promises

const promiseToReader = new Promise((resolve, reject) => {

setTimeout(function() {
if (userLikedTheArticle) {
resolve('This article is awesome!')
} else {
reject('I should have never been here! ;p')
}
}, enoughToReadArticle)

})

What is a Promise?

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and it's resulting value. More precisely is a proxy for a value not necessarily known when the promise is created. It allows for asynchronous action's eventual success value or failure reason. To put it into easier words, though not technically correct, it's like a function, which would run in parallel, and return its value once it's done, allowing the block who made the call to continue even though the result of the function is not yet available.

  • fulfilled: meaning that the operation completed successfully.
  • rejected: meaning that the operation failed.

How do we create Promises?

Let’s start at the construction definition:

/**
* Creates a new Promise.
*
@param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used to resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
const welcomeToPromises = new Promise((resolve, reject) => {
// This is the body if the asynchronous operation
// When this operation finishes we can call resolve(...) to set the Promise as fulfilled
// In case of failure, we can call reject(...) to set the Promise as rejected

// Let's do now something async, like waiting for a second
// Though in reallity you would probably do a remote operation, like XHR or an HTML5 API.
setTimeout(() => {
resolve('Sucess!') // Super! all went well!
}, 1000)
}

Consuming Promises, then and catch

We use the object methods then and catch to work with promises. The method then will register a callback, which will be executed automatically after the promise gets either resolved or rejected, while the method catch will register a callback, which will be fired when the Promise fails.

/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
*
@param onfulfilled The callback to execute when the Promise is resolved.
*
@param onrejected The callback to execute when the Promise is rejected.
*
@returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;


/**
* Attaches a callback for only the rejection of the Promise.
*
@param onrejected The callback to execute when the Promise is rejected.
*
@returns A Promise for the completion of the callback.
*/
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
promiseToReader.then(result => {
console.log('result', result)
}, error => {
console.error('error in then', error)
})

promiseToReader.catch(error => {
console.error('error', error)
})
console.log('Initiating test...')
console.log('Good reader started reading...')
test(true)
console.log('Probably what is a bot started reading...')
test(false)
console.log('Both users are now reading, and soon I should get my results')
Initiating test...
Good reader started reading...
Probably what is a bot started reading...
Both users are now reading, and soon I should get my results
result This article is awesome!
error in then I should have never been here! ;p
error I should have never been here! ;p

Promise Chaining

Because .then() or .catch() always return a new promise, it's possible to chain promises with precise control over how and when errors are handled. Why would this be helpful? Imagine a situation where the user enters a URL on a form, and we need to retrieve and process information from that URL, how would you go about doing that? It could be something like this:

fetch(url)
.then(validate)
.then(process)
.catch(handleErrors)

Avoid code duplication with Finally

In addition to then and catch, promises expose a 3rd method to register a callback, finally, with the following declaration syntax:

finally?<U>(onFinally?: () => U | Promise<U>): Promise<U>;
promiseToReader.finally(function() {
// settled (fulfilled or rejected)
console.log("Finally settled!")
});

How do I cancel a Promise?

The short answer is: you don’t! Promises by design cannot be canceled, however, some very clever people thought of some methods which can simulate a cancellation. There’re even libraries which can help with this task, but since in essence the promise is never canceled I won’t cover them here.

Extras

In addition to the promise object, Promise offers a series of static methods that can help you work easier with promises, let's take a look into them:

  • Promise.allSettled(iterable): Wait until all promises have settled (each may resolve or reject).
    Returns a promise that resolves after all of the given promises have either resolved or rejected, with an array of objects that each describe the outcome of each promise.
  • Promise.race(iterable): Wait until any of the promises is resolved or rejected.
    If the returned promise resolves, it is resolved with the value of the first promise in the iterable that resolved.
    If it rejects, it is rejected with the reason from the first promise that was rejected.
  • Promise.reject(reason): Returns a new Promise object that is rejected for the given reason.
  • Promise.resolve(value): Returns a new Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value.
    Generally, if you don’t know if a value is a promise or not, Promise.resolve(value) it instead and work with the return value as a promise.

Async / Await

When chaining promises, code can get out of hand, and it could be very hard to read. So some people thought of another way to work with promises, using async functions and the await operator.

  • Async function: is a function declared with the async keyword. Async functions are instances of the AsyncFunction constructor, and the await keyword is permitted within them. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.
function resolveAfter2Seconds() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}

async function asyncCall() {
console.log('calling');
const result = await resolveAfter2Seconds();
console.log(result);
// expected output: 'resolved'
}

asyncCall();
> "calling"
> "resolved"

Conclusion

Promises are widely used in JavaScript, and today most browsers would offer a native implementation of the Promise API, only older browsers such as some version of IE would require a polyfilll or similar to work. Promises are very fun to work with, though can be really hard to grasp initially. Practice, practice and practice, there are many use cases for this feature, and with no doubt any JS developer should know them in detail. It's a classic interview question, so be prepared.

I’m an entrepreneur, developer, author, speaker, and doer of things. I write about JavaScript, Python, AI, and programming in general.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store