JS Promises #2: how to get current promise status and build your own promise queue?
Tags: javascript, beginners, promise, programming
Published: 2022-08-05 20:57:00

[1. How promises work](https://dev.to/xnimorz/101-series-promises-1-how-promises-work-1k1i)

The first article described the base information about promises. 
Before we get to more complicated topics, I'd suggest thinking about the tools and utility functions we can create on top of promises. For this article let's think about getting the current promise status and building a promise queue.

### TL&DR

{% spoiler Check promise status %} 
```js
const pending = {
  state: 'pending',
};

function getPromiseState(promise) {
  // We put `pending` promise after the promise to test, 
  // which forces .race to test `promise` first
  return Promise.race([promise, pending]).then(
    (value) => {
      if (value === pending) {
        return value;
      }
      return {
        state: 'resolved',
        value
      };
    },
    (reason) => ({ state: 'rejected', reason })
  );
}
```
{% endspoiler %}
{% spoiler Promise queue %} 
```js
class Queue {
  _queue = Promise.resolve();

  enqueue(fn) {
    const result = this._queue.then(fn);
    this._queue = result.then(() => {}, () => {});

    // we changed return result to return result.then() 
    // to re-arm the promise
    return result.then();
  }

  wait() {
    return this._queue;
  }
}
```
{% endspoiler %}

### How to get current promise status

After you created a promise, you cannot get an info at runtime whether the promise is still `pending`, `fulfilled` or `rejected`. 

You can see the current promise status in debugger:
![DevTools shows the promise status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qn2t3t9sd3jbthpjmg1.png)
 
From the debugger we see that the Symbol `[[PromiseState]]` is in charge of that. However, `PromiseState` is not [well-known symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#well-known_symbols), so we can treat this state, as a private field which we can't get "right now". 

However, we can use `Promise.race` to test the current promise status. To do so, we may use the feature of `Promise.race`: 

📝 Promise.race checks promises in their order. For example:
```js
const a = Promise.resolve(1);
const b = Promise.resolve(2);
Promise.race([a, b]).then(console.log); // 1
```
And at the time:
```js
const a = Promise.resolve(1);
const b = Promise.resolve(2);
Promise.race([b, a]).then(console.log); // 2
```

📝 This code tests the promise status:
```js
const pending = {
  state: 'pending',
};

function getPromiseState(promise) {
  // We put `pending` promise after the promise to test, 
  // which forces .race to test `promise` first
  return Promise.race([promise, pending]).then(
    (value) => {
      if (value === pending) {
        return value;
      }
      return {
        state: 'resolved',
        value
      };
    },
    (reason) => ({ state: 'rejected', reason })
  );
}
```

Usage https://codesandbox.io/s/restless-sun-njun1?file=/src/index.js 
```js
(async function () {
  let result = await getPromiseState(Promise.resolve("resolved hello world"));
  console.log(result);

  result = await getPromiseState(Promise.reject("rejected hello world"));
  console.log(result);

  result = await getPromiseState(new Promise(() => {}));
  console.log(result);

  result = await getPromiseState("Hello world");
  console.log(result);
})();
```

In addition to the check, this feature of `Promise.race` can be helpful to run some code with timeouts. E.g:
```js
const TIMEOUT = 5000;
const timeout = new Promise((_, reject) => setTimeout(() => reject('timeout'), TIMEOUT));

// We may want to test check if timeout is rejected 
// before time consuming operations in the async code
// to cancel execution
async function someAsyncCode() {/*...*/}

const result = Promise.race([someAsyncCode(), timeout]);
```

### How to build your own promise queue

Sometimes you have to execute different code blocks in some order one-after-another. It's useful, when you have many heavy async blocks, which you want execute sequentially. We should remember, that:
📝 JS is single-threaded and therefore, we can have concurrent execution, but not parallel. 

To do so, we can implement promise queue. This queue would put every function after all previously added async functions. 

Let's check this codeblock:
```js
class Queue {
  // By default the queue is empty
  _queue = Promise.resolve();

  enqueue(fn) {
    // Plan new operation in the queue
    const result = this._queue.then(fn);
    
    // avoid side effects. 
    // We can also provide an error handler as an improvement
    this._queue = result.then(() => {}, () => {});
   
    // To preserve promise approach, let's return the `fn` result
    return result;
  }

  // If we want just to understand when the queue is over
  wait() {
    return this._queue;
  }
}
```

Let's test it:
```js
// Work emulator
const emulateWork = (name, time) => () => {
    console.log(`Start ${name}`);
    return new Promise((resolve) => {setTimeout(() => console.log(`End ${name}`) || resolve(), time)})
}

// Let's check if the queue works correctly if the promise fails
const failTest = () => () => {
    console.log(`Start fail`);
    return Promise.reject();
}
const queue = new Queue();
queue.enqueue(emulateWork('A', 500));
queue.enqueue(emulateWork('B', 500));
queue.enqueue(emulateWork('C', 900));
queue.enqueue(emulateWork('D', 1200));
queue.enqueue(emulateWork('E', 200));
queue.enqueue(failTest());
queue.enqueue(emulateWork('F', 900));
```

We would have the output:
![Promise Queue output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/saym4ah7wixxdogcrrcx.png)

However, when the promise gets rejected, we swallowed the error completely. 
It means, if the real code fails, we may never know about it. 

📝 If you work with promises, remember to use `.catch` at the end of the promise chain, otherwise you may miss failures in your application!

To fix the situation we can either provide a callback for the constructor:
```js
class Queue {
  _queue = Promise.resolve();
  
  // By default onError is empty
  _onError = () => {};

  constructor(onError) {
    this._onError = onError;
  }

  enqueue(fn) {
    const result = this._queue.then(fn);
    
    this._queue = result.then(() => {}, this._onError);
   
    return result;
  }

  wait() {
    return this._queue;
  }
}
```

Or we can re-arm the promise!

```js
class Queue {
  _queue = Promise.resolve();

  enqueue(fn) {
    const result = this._queue.then(fn);
    this._queue = result.then(() => {}, () => {});

    // we changed return result to return result.then() 
    // to re-arm the promise
    return result.then();
  }

  wait() {
    return this._queue;
  }
}
```
The same test would report a error!
![Error handling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hm1idwr9zcwlqx9xxme8.png)

📝 Every promise chain may cause an unhandledRejection error.

If you want to experiment with the code we build: https://codesandbox.io/s/adoring-platform-cfygr5?file=/src/index.js

### Wrapping up

Promises are extremely helpful when we need to resolve some complex async interactions. 

In the next articles we will talk why JS promises are called sometimes as `thenable` objects and continue our experiments.