Gajus
Gajus

Reputation: 73808

How to detect a promise that will never resolve?

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

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

main();

Suppose the above code fragment, where foo is a mishandled promise returned by some third-party code: How do I detect that foo is never going to be resolved?

For a bit of context, I am trying to work around this bug in a third-party package.

Upvotes: 0

Views: 335

Answers (1)

Gajus
Gajus

Reputation: 73808

I think this is the closest one will get to detecting if Promise will ever resolve.

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.

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

Upvotes: 3

Related Questions