A quick introduction to Promises and Async/Await

In this lesson, we are going to learn about ES6 Promises implementation in TypeScript and async/await syntax.

Promises are one of the newest features introduced in the JavaScript language. Since I have another article just on Promises and Async/Await syntax in JavaScript, I am just going to focus on how Promises are implemented in TypeScript.

💡 Please follow this above article to know more about how Promises work under the hood in the JavaScript engine. Having the knowledge Promises absolutely necessary to make sense of whatever we are going to talk about in this lesson.


A promise is an instance (object) of the Promise class (constructor). To create a promise, we use new Promise(*executor*) syntax and provide an executor function as an argument. This executor function provides a means to control the behavior of our promise resolution or rejection.

In TypeScript, we can provide the data type of the value returned when promise fulfills. Since the error returned by the promise can take any shape, the default data type of value returned when the promise is rejected is set to any by the TypeScript.

To annotate the resolution value type of the promise, we use a generic type declaration. Basically, you promise a type with Promise constructor in the form of new Promise<Type>() which indicates the resolved value type of the promise. But you can also use let p: Promise<Type> = new Promise() syntax to achieve the same.

💡 We have discussed generics classes in detail in the Generics lesson.

In the above example, findEven is a promise which was created using the Promise constructor that resolves after 1 second. The resolved data type of this promise is number, hence the TypeScript compiler won’t allow you to call resolve function with a value other than a value of type number number.

The default type of the promise’s rejection value is any, hence calling reject function with any value is legal. This is the default behavior of TypeScript, and you can find the discussion thread here if you have your own opinions.

Since we have provided the number as the data type of successful promise resolution, the TypeScript compiler will provide the number type to the argument of value argument of the then callback method.

The callback provided in the then method is executed when the promise is resolved and the callback provided in catch method is executed when it rejects or some error while resolving the promise. The finally method registers a callback that executes when promise either resolves or rejects.

If the TypeScript compiler complains about the finally method, that means your TypeScript compiler doesn’t import type definitions for the finally method. This method was introduced in ES2016, hence it’s quite new. Other features of the Promise API used in this lesson are pretty new, hence make sure your tsconfig.json file has all the new libraries loaded.

In my tsconfig.json, I have loaded the ES2020 standard library. This provides support for all the JavaScript feature up until ES2020. If you want to know more about the tsconfig.json file or standard libraries, please read the Compilation lesson (coming soon).

Promise Chaining

The then, catch and finally methods return a promise implicitly. Any value returned by these callback functions is wrapped with a promise and returned, including undefined. This implicit promise is resolved by default unless you are deliberately returning a new promise from these methods that could fail.

Hence you can append then, catch or finally methods to any of the previous then, catch or finally method. If an implicit promise is returned by one of these methods, then the resolved value type of this implicit promise is the type of the returned value. Let’s see a quick example.

We have modified the previous example and added another then method to the first then method. Since the first then method returns a value of type string, the implicit promise returned by this method will be resolved with a value of the type string. Hence, the second then method will receive value argument of type string as you can see from the results.

Promise.resolve

The resolve static method of the Promise call returns a promise that is already resolved successfully with a value that you provide in the Promise.resolve(value) call. This is easier than creating a new instance of the Promise call and adding logic to resolve the promise immediately.

As you can see from the above results, the promise returned by the Promise.resolve(value) call always resolves immediately with a number value since the value argument has the type of number.

Promise.reject

Similar to Promise.resolve static method, the Promise.reject(error) method always returns a rejected promise. The value of the promise rejection is taken from the error_ argument and its type is any._

The type of promise returned by the Promise.reject method is Promise<never> because this promise never resolves, so there won’t be any promise resolution value. Therefore the type of the value resolved by the promise is never as never signifies the value that never occurs.

Promise.all

In some scenarios, you are dealing with multiple promises. If you want to execute a callback function when all the promises are resolved successfully, use the Promise.[all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) static method.

var pAll = Promise.all([ p1, p2, ... ])
pAll.then( ( [ r1, r2, ... ] ) => {___});

The Promise.all method takes an array (iterable precisely) of promises and returns a new promise. The returned promise pAll is resolved when all promises p1, p2, ... are resolved successfully. This promise is resolved with an array value that contains the promise resolution values of p1, p2, ... in the order of their appearance.

As you can see from the above example, Promise.all method is generic and it takes the type the value resolved by each promise provided to it. Providing a generic type is quite helpful when we are using the resolved value of this collective promise as you can see from the above result.

💡 Most of the static methods of the Promise class are generic as demonstrated in the below examples.

There is one caveat with Promise.all. It implements the fail-fast mechanism which means if any of the input promises p1, p2, ... are rejected, pAll is rejected. If won’t wait for other pending promises to resolve.

As you can see from the above result, as the 3rd promise was rejected just after 1 second, the allPromise was rejected immediately.

Promise.allSettled

The Promise.allSettled static method is similar to Promise.all but unlike Promise.all, it waits until all the promises are settled (which means until they resolved or rejected). Hence the promise returned by Promise.allSettled will never reject (but we have added catch block in the below example anyway).

Working with promise.allSettled can be a little overwhelming as you can from the above program and its result. First of all, allSettle method returns a promise that resolves with an array of PromiseSettledResult<type> values when all promises are settled. The type is derived from the type of the input promise. The PromiseSettledResult type looks like below.

interface PromiseFulfilledResult<T> {
  status: "fulfilled";
  value: T; // promise resolved value
}

interface PromiseRejectedResult {
  status: "rejected";
  reason: any; // promise rejected value
}

type PromiseSettledResult<T> =
  | PromiseFulfilledResult<T>
  | PromiseRejectedResult;

These types are provided by TypeScript’s standard library. So when a promise resolves, allSettled method converts its value into PromiseFulfilledResult shape and when it fails, it converts it to PromiseRejectedResult shape. That’s why when allSettled is resolved, it’s an array of objects in which each object has a shape of PromiseFulfilledResult or PromiseRejectedResult interface.

Since PromiseFulfilledResult is a union of PromiseFulfilledResult and PromiseRejectedResult that has common status property of literal data type, we can use it as a discriminant in the switch/case guard.

💡 We have talked about switch/case type guard and discriminating unions in the Type System lesson.

Promise.race

The Promise.race takes an array (iterable precisely) of promises and returns a new promise that resolves or rejects as soon as one of the input promises resolves or rejects. In other words, the promise returned by Promise.race is settled with the result of one of the input promises which settles quickly.

Unlike Promise.all or Promise.allSettled, this method only returns a single value of the first settled promise, hence the type of the returned promise is Promise<number> in the above case. Since the 1st promise settled first among others, the then callback of the fastestPromise gets called after 500ms with the value of the resolved promise.

💡 The new Promise.any() method has reached Stage 4 of the ECMAScript proposal track. Promise.any is much like Promise.race but it waits until the first promise is successful resolves. This method throw an AggregateError exception if all the promises are rejected.

Async/Await keywords

Writing promises the normal way seems a bit difficult to manage. There is a lot of unnecessary boilerplate code involved. Normally, you have a function that processes some arguments and returns a promise that resolves or rejects with some meaningful data. Adding a promise creation mechanism using Promise constructor seems just icky at times.

JavaScript provide async keyword to turn a normal function into a function that returns a promise implicitly. Any value returned by this async function will be converted to an implicit promise that resolves with this value. If an error is thrown in this function, the returned promise is rejected with the error message.

The await keyword is used inside an async function to wait on a promise. If a promise has the await keyword before it, the function execution won’t proceed further until that promise is resolved.

In the above example, the getRandomInt is an async function since it has async keyword before the function expression. Since this function returns a promise which resolves with a number value, we have provided the correct type for the getRandomInt value (shown below).

const getRandomInt: () => Promise<number>;

However, this is not necessary. TypeScript understands the async keyword, as well as looks at the return value type of the function to provide an implicit type for the function. Hence, the isEven constant has the below type.

const isEven: (answer: boolean) => Promise<boolean>;

We have used await keyword inside the isEven function expression to wait for the resolution of the promise returned by the getRandomInt function. This means isEven function execution is basically going to halt until this promise is resolved, including the value assignment expression.

Since TypeScript knows about the type of the promise returned by the getRandomInt function, the value constant has the number type. The isEven async function returns a promise that resolved with a boolean value.

You might wonder, how the promise returned by a async function is going to reject? Well, I have explained this in my other article on Promises but in a nutshell, the promise returned by an async function rejects if an error is thrown inside the async function.

xIn the above example, we have done some modifications to the previous example. We have added a check inside isEven function that checks if the value is 0 then throws a generic JavaScript error.

In this scenario, there is a chance that the promise returned by the isEven function may get rejected. This will be caught by the catch method and it will contain the error that was thrown in the async function.

We have provided the Error type for the error argument of the callback function of the catch method to assert the default any type to Error type since we know the rejection value type beforehand.

Since an async function uses throw keyword to throw an error in order to reject the promise, it raises another question. What will happen if another async function is awaiting on the promise if that promise gets rejected?

The short answer is, errors are bubbled up in the async functions. This means if the promise we are awaiting (using await keyword) rejects, JavaScript will throw an error in the current async function and nothing below that await keyword will execute. This goes on if the current async function is also being used inside another async function.

To check if the awaited promise was rejected, we use try/catch. If we await a promise inside a try block, we will know if the promise was rejected in the catch block. The argument to catch block is the rejection value.

💡 FYI, you can also await a promise returned by the Promise constructor. There won’t be any difference whatsoever. If it rejects, it will result in an error.

In the above example, the promise returned by the getRandomInt can get rejected. Hence inside the isEven async function, we have wrapped the awaiting logic inside the try block. If the code inside try block throws an error, the catch block will be executed with that error.

In this example, the isEven async function always returns a promise that never rejects. If you want to reject this promise, you need to throw an error manually using throw keyword within the catch block.

#typescript #promises