Forever Functional: The hidden state of Promises
Tags: javascript, promises, programming, functional
Published: 2023-02-14 00:41:56

by [Federico Kereki](https://blog.openreplay.com/authors/federico-kereki)


When you work with promises, you know that they may be in different states, and sometimes you may need to learn the specific status of a given promise -- but how to get it? JavaScript provides no ready, available solution for this problem! 

So, in this article we're going to figure out a way to determine the current status of a promise by using only available, standard properties and methods, so our code will be sure to work everywhere.

## What are the possible statuses of a promise?

Promises can be in three states:
* `Pending`, when they haven't yet resolved to a value or rejected with an error
* `Fulfilled`, when they have resolved to a value
* `Rejected`, when they have rejected

Pending promises are said to be "`unsettled`" while fulfilled and rejected promises are "settled`". 

We can readily see all three states with little coding:


```javascript
const a = new Promise((resolve,reject) => { /* nothing */ });
// Promise {<pending>}
const b = Promise.resolve(22);
// Promise {<fulfilled>: 22}
const c = Promise.reject("bad");
// Promise {<rejected>: 'bad'}
```

It's evident that promises internally keep track of their status in some way, but there are no visible, public properties we can access for it. If we try `Object.keys(somePromise)`,  we'll get an empty array.

So, if we want to get the status of a promise, we need to find some way to do so by using the available functions; there's no "backdoor" we can use for it. Fortunately, there's a way to achieve this, as we'll see next.

## Implementing our method

Following the example of what we did in our previous [*Waiting For Some Promises?*](https://blog.openreplay.com/forever-functional-waiting-for-some-promises/) article, we'll define an independent function and also modify the `Promise.prototype` to simplify getting the desired status.

```javascript
Promise.prototype.status = function () {
  return promiseStatus(this);
};
```

The issue here is that we won't be able to get the status synchronously; we'll have to write an `async` function for that. How? We can use [`Promise.race()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race) for that.

When you call `Promise.race()` with an array of promises, it settles as soon as any promise is settled. With that in mind, we can call it providing *two* promises: the one we care about and an already settled one. How would this work?

* If `Promise.race()` resolves to the value returned by our already settled promise, the other is pending.
* If `Promise.race()` resolves to anything else, it is either with a value (so, the promise is fulfilled) or to an error with some reason (the promise was rejected.)

So, we can write the following:

```javascript
const promiseStatus = async (p) => {
  const SPECIAL = Symbol("status");

  return Promise.race([p, Promise.resolve(SPECIAL)]).then(
    (value) => (value === SPECIAL ? "pending" : "fulfilled"),
    (reason) => "rejected"
  );
};
```


(A tip for keen-eyed JavaScripters: we didn't really need to specify `async` in the first line; a function that returns a promise is, by definition, already `async`.)

How does this work? We must ensure that our already settled promise resolves to a value no other code can resolve to. Using a [`symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) is the key; symbols are guaranteed to be unique.

So, if `Promise.race()` resolves to some value, if it was our symbol, it means that the promise we wanted to test is still pending. Otherwise, if the race resolves to any other value, the other promise is fulfilled. On the other hand, if the race ends with a rejection, we know for sure the tested promise is rejected. (You may want to review how [`.then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) works.)

We did it! Our `promiseStatus()` method is an `async` one, but it is settled immediately. Let's see how to test it!

## Testing our method

First, we'll need some dummy promises, and we can reuse the fake ones we worked with in the [*Waiting For Some Promises?*](https://blog.openreplay.com/forever-functional-waiting-for-some-promises/) article.

```javascript
const success = (time, value) =>
  new Promise((resolve) => setTimeout(resolve, time, value));

const failure = (time, reason) =>
  new Promise((_, reject) => setTimeout(reject, time, reason));
```

The `success()` function returns a promise that will resolve to a value after some time, and the `failure()` function returns a promise that will reject with a reason after some time. Now we can write the following tests, which could be transformed into [Jest](https://jestjs.io/) unit tests if we so desire.

```javascript
const ppp = success(200, 33);
const qqq = failure(500, "Why not?");

promiseStatus(ppp).then(console.log);   // pending
promiseStatus(qqq).then(console.log);   // pending

ppp.then(() => {  
  promiseStatus(ppp).then(console.log); // fulfilled
  promiseStatus(qqq).then(console.log); // pending
});

qqq.catch(() => {
  promiseStatus(ppp).then(console.log); // fulfilled
  promiseStatus(qqq).then(console.log); // rejected
});
```

Let's analyze the results. The first two tests produce "pending" results because no promise has had time to settle yet. If we wait until `ppp` has resolved, its status becomes "fulfilled", but `qqq` is still "pending". If we then wait until `qqq` is settled, `ppp` is still "fulfilled" (no change there) but now `qqq` is "rejected" -- all correct!


<h2>Session Replay for Developers</h2> 
<p><em>
Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data
</em></p>
<img alt="OpenReplay" width="768" height="400" src="https://blog.openreplay.com/assets/overview_Z17Qdyg.png" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em>




## Typing our method

We must start with our `promiseStatus()` function. We can define an auxiliary `PromiseStatus` type with the three possible results. Our `promiseStatus()` function will take a `Promise<any>` as a parameter and return a `Promise<PromiseStatus>` result.

```typescript
type PromiseStatus = "pending" | "fulfilled" | "rejected";

const promiseStatus = (p: Promise<any>): Promise<PromiseStatus> => {
  const SPECIAL = Symbol("status");

  return Promise.race([p, Promise.resolve(SPECIAL)]).then(
    (value) => (value === SPECIAL ? "pending" : "fulfilled"),
    (reason) => "rejected"
  );
};
```

To modify the `Promise.prototype`, we must add a global definition as follows.

```typescript
declare global {
  interface Promise<T> {
    status(): Promise<PromiseStatus>;
  }
}
```

With this definition, we can now add our function to the `Promise.prototype`.

```typescript
Promise.prototype.status = function () {
  return promiseStatus(this);
};
```

With this, we would be able to work directly with the status method, as in:

```javascript
ppp.status().then(console.log); 
```

or

```javascript
const rrr = await qqq.status();
if (rrr === ...) { ... }
```

We're done!

## Conclusion

In this article, we've tackled the problem of learning a promise's status and found a roundabout way to get at it because of limitations that didn't allow us to directly access it. We've also produced a TypeScript fully typed version to make the code more useful. We'll be returning to promises in future articles; there's more to be said!

> A TIP FROM THE EDITOR: We've covered promises in more articles, such as [Waiting With Promises](https://blog.openreplay.com/forever-functional-waiting-with-promises/) and [Waiting For Some Promises?](https://blog.openreplay.com/forever-functional-waiting-for-some-promises/); don't miss them!



[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)