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.
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.
poll-until-promise is built for general async logic.
// 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.
// 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();
});
});
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.
interval (ms between checks) and limit (max attempts).// 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.
timeout (total ms) and interval (ms between checks).// wait-for-expect: Custom timeout settings
await waitForExpect(
() => expect(value).toBe(10),
5000, // timeout
200 // interval
);
How each package fails tells you a lot about where to use them.
poll-until-promise throws when the limit is reached.
// 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.
// 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
}
It is important to consider the long-term health of these dependencies.
poll-until-promise remains a lightweight utility.
wait-for-expect is largely legacy in modern React testing.
waitFor, which does the same thing.@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();
});
| Feature | poll-until-promise | wait-for-expect |
|---|---|---|
| Primary Use | Production / Script Polling | Test Assertions |
| Return Value | Returns resolved data | Returns void (passes or throws) |
| Failure Mode | Rejects Promise | Throws Assertion Error |
| Dependencies | Minimal / None | None (but legacy) |
| Modern Alternative | Native loops / p-retry | @testing-library/react waitFor |
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.
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.
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.
Wait until the executed promise resolved to a true value, Execute it every x milliseconds and stop after y milliseconds.
npm install poll-until-promise
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));
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;
}
}
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));
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));
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);
pollUntilPromise.isResolved()
pollUntilPromise.isWaiting()
pollUntilPromise.getPromise().then(() => console.log('OMG'))
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));