poll-until-promise vs wait-for-expect
Async Polling and Test Assertion Utilities in JavaScript
poll-until-promisewait-for-expect

Async Polling and Test Assertion Utilities in JavaScript

poll-until-promise and wait-for-expect both handle asynchronous waiting, but they serve different stages of the development lifecycle. poll-until-promise is a general-purpose utility designed for production or script environments where you need to repeatedly check a condition until it resolves, such as polling an API for a job status. wait-for-expect is a testing utility primarily used in Jest environments to wait for an assertion to pass without failing the test immediately, often serving as a bridge before native testing library features became standard.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
poll-until-promise02047.9 kB23 years agoISC
wait-for-expect029938.3 kB10a year agoMIT

Async Polling and Test Assertion Utilities: poll-until-promise vs wait-for-expect

When dealing with asynchronous operations in JavaScript, timing is everything. Whether you are waiting for a backend job to finish or ensuring a UI element has rendered in a test, you need reliable tools to handle delays. poll-until-promise and wait-for-expect both solve waiting problems, but they target different environments and use cases. Let's explore how they work and where they fit in your stack.

🎯 Core Purpose: Production Polling vs Test Assertions

poll-until-promise is built for general async logic.

  • It repeatedly executes a function until it returns a resolved promise or a truthy value.
  • Ideal for production code, CLI tools, or E2E scripts where you control the flow.
  • Focuses on getting a result from an external source.
// poll-until-promise: Checking an API status
import pollUntilPromise from 'poll-until-promise';

const checkJobStatus = async () => {
  const res = await fetch('/api/job/123');
  const data = await res.json();
  if (data.status !== 'completed') throw new Error('Not done');
  return data;
};

// Polls every 1s until success or timeout
const result = await pollUntilPromise(checkJobStatus, { interval: 1000 });

wait-for-expect is built for testing stability.

  • It waits for a test assertion to stop throwing errors.
  • Ideal for unit or integration tests where UI or async state is settling.
  • Focuses on validating a condition rather than retrieving data.
// wait-for-expect: Waiting for a value to update in a test
import waitForExpect from 'wait-for-expect';

it('updates the count after fetch', async () => {
  render(<Counter />);
  fireEvent.click(screen.getByText('Load'));
  
  // Waits up to 4.5s for the expectation to pass
  await waitForExpect(() => {
    expect(screen.getByText('Count: 1')).toBeInTheDocument();
  });
});

⚙️ Configuration and Control

Both packages allow you to tune how long they wait and how often they check, but their options reflect their different goals.

poll-until-promise focuses on request timing.

  • You define the interval (ms between checks) and limit (max attempts).
  • It returns the successful result directly.
// poll-until-promise: Custom timeout and interval
const data = await pollUntilPromise(
  () => fetchStatus(), 
  { interval: 500, limit: 10 } // 5 seconds total max
);

wait-for-expect focuses on test timeout thresholds.

  • You define timeout (total ms) and interval (ms between checks).
  • It returns nothing; it either passes or throws an error.
// wait-for-expect: Custom timeout settings
await waitForExpect(
  () => expect(value).toBe(10),
  5000, // timeout
  200   // interval
);

❌ Error Handling and Failure Modes

How each package fails tells you a lot about where to use them.

poll-until-promise throws when the limit is reached.

  • If the condition is never met, it rejects the promise.
  • You catch this like any other async error in your app logic.
// poll-until-promise: Handling failure
try {
  await pollUntilPromise(checkService, { limit: 5 });
} catch (error) {
  console.error('Service never became ready:', error);
  // Fallback logic here
}

wait-for-expect fails the test case immediately.

  • If the timeout is hit, it throws an assertion error.
  • This stops the test suite, signaling a bug or timing issue.
// wait-for-expect: Test failure
try {
  await waitForExpect(() => expect(element).toBeVisible());
} catch (error) {
  // Test fails here, reporting the expectation mismatch
  // No fallback logic needed in tests
}

🛠️ Maintenance and Modern Alternatives

It is important to consider the long-term health of these dependencies.

poll-until-promise remains a lightweight utility.

  • It has no heavy dependencies.
  • Suitable for long-term use in scripts or backend logic where you need simple polling without pulling in a large library.

wait-for-expect is largely legacy in modern React testing.

  • The Testing Library ecosystem now includes waitFor, which does the same thing.
  • New projects should prefer @testing-library/react over this standalone package.
// Modern alternative to wait-for-expect
import { waitFor } from '@testing-library/react';

// Same behavior, better ecosystem support
await waitFor(() => {
  expect(screen.getByText('Ready')).toBeInTheDocument();
});

📊 Summary Table

Featurepoll-until-promisewait-for-expect
Primary UseProduction / Script PollingTest Assertions
Return ValueReturns resolved dataReturns void (passes or throws)
Failure ModeRejects PromiseThrows Assertion Error
DependenciesMinimal / NoneNone (but legacy)
Modern AlternativeNative loops / p-retry@testing-library/react waitFor

💡 The Big Picture

poll-until-promise is a tool for building resilient applications.
Use it when your code needs to talk to external systems that take time to process. It keeps your production logic clean and avoids manual setInterval boilerplate.

wait-for-expect is a tool for stabilizing tests.
Use it if you are maintaining older Jest suites. However, for new development, reach for waitFor from Testing Library instead. It offers better integration with DOM testing and is actively maintained.

Final Thought: While both handle waiting, one is for getting work done (polling) and the other is for checking work (testing). Pick the one that matches your environment — application code or test suites.

How to Choose: poll-until-promise vs wait-for-expect

  • poll-until-promise:

    Choose poll-until-promise when you need to implement retry logic or polling mechanisms in application code, E2E scripts, or Node.js services. It is suitable for scenarios where you must wait for an external system to reach a specific state, like a file appearing in storage or a database record updating. This package is appropriate when you are not inside a test runner and need a standalone promise-based polling solution.

  • wait-for-expect:

    Choose wait-for-expect primarily for legacy Jest test suites that require waiting for asynchronous assertions to stabilize. However, if you are using React Testing Library or modern testing tools, prefer their built-in waitFor function instead. Use this package only if you are maintaining older tests or need a lightweight assertion waiter without the overhead of a full testing library DOM dependency.

README for poll-until-promise

NPM Version Build

Poll Until Promise

Wait until the executed promise resolved to a true value, Execute it every x milliseconds and stop after y milliseconds.

Install

npm install poll-until-promise

Usage

Fetching data

const { waitFor } = require('poll-until-promise');

waitFor(() => fetch('/get-data'), { interval: 100 })
    // Tries every 100ms (from the last failure)
    .then(value => console.log('Yey', value))
    .catch(err => console.error(err));

Using async

import { waitFor, AbortError } from 'poll-until-promise';

async function waitForDomElement(cssSelector = 'div') {
  try {
    const element = await waitFor(() => {
      const element = window.document.querySelector(cssSelector);
      if (!element) throw new Error(`failed to find element: ${cssSelector}`);
      return element;
    }, { timeout: 60_000 });

    return element;
  } catch (e) {
    console.error('faled to find dom element:', e);
    throw e;
  }
}

async function retryFetch(path = '/get-data') {
  try {
    const data = await waitFor(async () => {
      const res = await fetch(path);

      // Stop immediately if the resource doesn't exist
      if (res.status === 404) {
        throw new AbortError(res.statusText);
      }

      return res.json();
    }, { timeout: 60_000, interval: 1000 });
  } catch (e) {
    console.error('faled to fetch:', e);
    throw e;
  }
}

Waiting for something to be successful

const { waitFor } = require('poll-until-promise');

waitFor(() => {
  if (Math.random() >= 0.5) {
    throw new Error('try again');
  } else {
    console.log('all good')
  }
})
  .then(() => console.log('Yey'))
  .catch(err => console.error(err));

Using the class

const { PollUntil } = require('poll-until-promise');

const later = Date.now() + 1000; // 1 seconds into the future

let pollUntilPromise = new PollUntil();
pollUntilPromise
    .stopAfter(2 * 1000)    // Stop trying after 2 seconds
    .tryEvery(100)          // Tries every 100ms (from the last failure)
    .execute(() => {
        return new Promise((resolve, reject) => {
            if (+Date.now() >= later) {
                return resolve(true); // Some truthy value
            }
            reject(false);
        })
    })
    .then(value => console.log('Yey', value))
    .catch(err => console.error(err));

Options

const options = {
    interval: 100,
    backoffFactor: 1, // Exponential interval increase. Defaults to 1, which means no backoff
    backoffMaxInterval: 250, // Sets a maximum interval when using backoff. Defaults to the timeout value
    timeout: 1000,
    stopOnFailure: false, // Ignores promise rejections
    verbose: false,
    message: 'Waiting for time to pass :)', // custom message to display on failure
    maxAttempts: 5, // maximum attempts to make (default: no limit). Will still fail to wait if reaching timeout before attempts exhausted
};
let pollUntilPromise = new PollUntil(options);

Methods

  • isResolved
pollUntilPromise.isResolved()
  • isWaiting
pollUntilPromise.isWaiting()
  • getPromise
pollUntilPromise.getPromise().then(() => console.log('OMG'))

Static Function

const PollUntil = require('poll-until-promise');
const later = Date.now() + 1000; // 1 seconds into the future

let pollUntilPromise = new PollUntil();
pollUntilPromise
    .stopAfter(2 * 1000)
    .tryEvery(100)
    .execute(() => {
        if (+Date.now() >= later) {
            return true;
        }
        return false;
    })
    .then((value) => console.log('Yey', value))
    .catch((err) => console.error(err));