Detecting a promise that will never resolve/ reject
Tags: javascript
Published: 2019-11-01 15:34:01

---
title: Detecting a promise that will never resolve/ reject
published: true
description: Using async_events to track Promise liveness.
tags: javascript
---

I ran into an interesting JavaScript problem today: How to detect a promise that will never resolve/ reject?

It is generally not desirable to ever have a promise that does not resolve/ reject. In this particular case, it was a [bug](https://github.com/sindresorhus/got/issues/907#issue-516073769) in a third-party package.

The underlying problem was an interesting challenge and it could be generalised as:

```js
const main = async () => {
  const foo = new Promise(() => {});

  foo
    .catch((error) => {
      console.log('error', error);
    })
    .then((response) => {
      console.log('response', response);
    });
};

main();

```

In this case `foo` is not going to resolve and it will not going to reject. In fact, because there is nothing keeping the event loop alive, the program will just quit.

The solution that I came up was to add a timeout and listen for asynchronous events created in the same asynchronous context as where the promise is created.


```js
const asyncHooks = require('async_hooks');

const timeoutIdlePromise = async (createPromise, maximumIdleTime) => {
  return new Promise(async (resolve, reject) => {
    let Timeout;

    const parentAsyncIds = [];

    const asyncHook = asyncHooks.createHook({
      init: (asyncId, type, triggerAsyncId) => {
        if (parentAsyncIds.includes(triggerAsyncId)) {
          if (Timeout) {
            Timeout.refresh();
          }

          if (!parentAsyncIds.includes(asyncId)) {
            parentAsyncIds.push(asyncId);
          }
        }
      },
    });

    Timeout = setTimeout(() => {
      reject(new Error('Idle promise timeout.'));

       asyncHook.disable();
    }, maximumIdleTime);

    asyncHook.enable();

    // Force new async execution context.
    await null;

    const executionAsyncId = asyncHooks.executionAsyncId();

    parentAsyncIds.push(executionAsyncId);

    try {
      const result = await createPromise();

      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      asyncHook.disable();
    }
  })
};

// Rejected with Idle promise timeout.
timeoutIdlePromise(() => {
  return new Promise((resolve) => {

  });
}, 1000);

// Resolved.
timeoutIdlePromise(() => {
  return new Promise((resolve) => {
    setTimeout(() => {
      setTimeout(() => {
        setTimeout(() => {
          resolve();
        }, 500);
      }, 500);
    }, 500);
  });
}, 1000);

```

`async_hooks` are used here to check if the promise is creating any asynchronous events (and if the asynchronous events created by the promise create other asynchronous events themselves, etc) As long as there is some asynchronous activity within the promise (e.g. event listeners, network activity, timeouts), it will continue to hang. It will throw an error if there is no asynchronous activity within maximumIdleTime.

I have abstracted the above logic into a module [`timeout-idle-promise`](https://github.com/gajus/timeout-idle-promise).

```js
import {
  timeoutIdlePromise,
  TimeoutError,
} from 'timeout-idle-promise';

// Rejected with TimeoutError error.
timeoutIdlePromise(() => {
  return new Promise((resolve) => {

  });
}, 1000);

// Resolved.
timeoutIdlePromise(() => {
  return new Promise((resolve) => {
    setTimeout(() => {
      setTimeout(() => {
        setTimeout(() => {
          resolve();
        }, 500);
      }, 500);
    }, 500);
  });
}, 1000);

```