DEV Community

Gajus Kuizinas
Gajus Kuizinas

Posted on

2

Detecting a promise that will never resolve/ reject

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 in a third-party package.

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

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

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

main();

Enter fullscreen mode Exit fullscreen mode

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.

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);

Enter fullscreen mode Exit fullscreen mode

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.

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);

Enter fullscreen mode Exit fullscreen mode

AWS Q Developer image

Your AI Code Assistant

Implement features, document your code, or refactor your projects.
Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay