Wait for external scripts, DOM elements, or custom conditions with a clean promise-based API.
- Promise-Based: Modern async/await API
- Flexible Conditions: Wait for any custom predicate
- DOM Support: Built-in element polling
- Global Variables: Wait for third-party scripts
- Immediate Check: Performance optimization (checks before polling)
- Event-Driven: Emits events for observability
- Lifecycle Integration: Auto-cleanup on SDK destroy
- Type-Safe: Full TypeScript support
pnpm add @prosdevlab/sdk-kit @prosdevlab/sdk-kit-pluginsimport { SDK } from '@prosdevlab/sdk-kit';
import { pollPlugin } from '@prosdevlab/sdk-kit-plugins/poll';
const sdk = new SDK({
poll: {
timeout: 5000, // 5 seconds (default)
interval: 100, // Check every 100ms (default)
immediate: true // Check immediately (default)
}
});
sdk.use(pollPlugin);
// Wait for condition
const success = await sdk.poll.waitFor(() => window.myApp?.ready);
// Wait for DOM element
const modal = await sdk.poll.element('#checkout-modal');
// Wait for global variable
const jQuery = await sdk.poll.global('window.jQuery');Wait for a custom condition to become true.
// Wait for app to initialize
const success = await sdk.poll.waitFor(() => {
return window.myApp &&
window.myApp.config &&
window.myApp.config.ready === true;
});
if (success) {
console.log('App is ready!');
} else {
console.log('Timeout - app never initialized');
}Parameters:
condition: () => boolean- Function that returns true when condition is metoptions?: PollOptions- Polling options
Returns: Promise<boolean> - true if condition met, false if timeout
Options:
interface PollOptions {
timeout?: number; // Max time to wait (ms), default: 5000
interval?: number; // Check interval (ms), default: 100
immediate?: boolean; // Check immediately, default: true
}Wait for a DOM element to appear.
// Wait for modal
const modal = await sdk.poll.element('#checkout-modal');
if (modal) {
modal.classList.add('active');
}
// With timeout
const button = await sdk.poll.element('.submit-btn', { timeout: 3000 });Parameters:
selector: string- CSS selectoroptions?: PollOptions- Polling options
Returns: Promise<Element | null> - The element or null if timeout
Wait for a global variable to be defined. Supports dot-notation for nested properties.
// Wait for jQuery
const jQuery = await sdk.poll.global('window.jQuery');
if (jQuery) {
jQuery('#element').hide();
}
// Wait for nested property
const gtag = await sdk.poll.global('window.gtag');
// Wait for deeply nested
const config = await sdk.poll.global('window.myApp.config.apiKey');Parameters:
path: string- Dot-notation path (e.g.,'window.jQuery','myApp.config.ready')options?: PollOptions- Polling options
Returns: Promise<unknown> - The value or null if timeout
Configure default polling behavior:
const sdk = new SDK({
poll: {
timeout: 10000, // Wait up to 10 seconds
interval: 200, // Check every 200ms
immediate: true // Check immediately before polling
}
});Options can be overridden per-call:
// Use custom timeout for this call
const success = await sdk.poll.waitFor(condition, { timeout: 3000 });The poll plugin emits events for observability:
// Polling started
sdk.on('poll:started', (data) => {
console.log(`Polling started: ${data.timeout}ms timeout`);
});
// Condition met
sdk.on('poll:succeeded', (data) => {
console.log(`Success after ${data.tries} tries in ${data.duration}ms`);
});
// Timeout reached
sdk.on('poll:timeout', (data) => {
console.log(`Timeout after ${data.tries} tries in ${data.duration}ms`);
});
// Cleanup on destroy
sdk.on('poll:cleanup', (data) => {
console.log(`Cleaned up ${data.cleared} active polls`);
});// Wait for Google Analytics
const gtag = await sdk.poll.global('window.gtag');
if (gtag) {
gtag('event', 'page_view', {
page_title: document.title,
page_location: window.location.href
});
}// Wait for jQuery to load
const jQuery = await sdk.poll.global('window.jQuery');
if (jQuery) {
jQuery('#element').fadeIn();
} else {
console.warn('jQuery never loaded');
}// Wait for checkout modal to appear
const modal = await sdk.poll.element('#checkout-modal', { timeout: 10000 });
if (modal) {
// Show exit-intent widget
showExitIntentWidget();
}// Wait for complex initialization
const ready = await sdk.poll.waitFor(() => {
const app = window.myApp;
return app &&
app.config?.loaded &&
app.modules?.initialized &&
app.user?.authenticated;
}, { timeout: 10000 });
if (ready) {
initializeFeatures();
}// Wait for user data from another script
const userData = await sdk.poll.global('window.currentUser');
if (userData) {
sdk.identify(userData.id, {
email: userData.email,
name: userData.name
});
}// Try with short timeout first, then longer
async function waitForDataWithRetry() {
// Quick check (2 seconds)
let data = await sdk.poll.global('window.userData', { timeout: 2000 });
if (!data) {
// Longer wait (10 seconds)
data = await sdk.poll.global('window.userData', { timeout: 10000 });
}
if (data) {
processUserData(data);
} else {
console.error('User data never loaded');
}
}// Wait for multiple things
const [hasJQuery, hasModal, hasData] = await Promise.all([
sdk.poll.global('window.jQuery'),
sdk.poll.element('#modal'),
sdk.poll.global('window.userData')
]);
if (hasJQuery && hasModal && hasData) {
console.log('Everything is ready!');
}// Wait for consent management platform
const oneTrust = await sdk.poll.global('window.OneTrust');
if (oneTrust) {
// Check if user has consented to analytics
const hasConsent = await sdk.poll.waitFor(() => {
return window.OnetrustActiveGroups?.includes('C0002');
});
if (hasConsent) {
initializeAnalytics();
}
}The poll plugin checks the condition immediately before starting the polling interval. This is a performance optimization:
// If jQuery is already loaded, returns immediately
const jQuery = await sdk.poll.global('window.jQuery');
// No delay! ✨Disable immediate check if you want to wait for a change:
// Wait for value to change (not just exist)
const success = await sdk.poll.waitFor(
() => window.myApp.status === 'ready',
{ immediate: false }
);- Short interval (50-100ms): More responsive, more CPU
- Long interval (200-500ms): Less CPU, slower to detect
- Timeout: Should be generous (5-10 seconds) for slow networks
// Responsive polling for fast checks
await sdk.poll.element('#button', { interval: 50, timeout: 2000 });
// Conservative polling for slow scripts
await sdk.poll.global('window.heavyLibrary', { interval: 500, timeout: 30000 });The poll plugin handles errors in conditions gracefully:
// This will throw initially (myApp is undefined)
const success = await sdk.poll.waitFor(() => {
return window.myApp.config.ready; // Throws if myApp is undefined
});
// No error thrown! Keeps polling until myApp existsWhy? When checking nested properties, they often don't exist yet. The plugin catches these errors and continues polling.
Timeouts return false, they don't throw errors:
const success = await sdk.poll.waitFor(condition);
if (success) {
// Condition met
} else {
// Timeout - handle gracefully
console.warn('Condition never met');
}Why? Timeouts are expected behavior, not exceptional errors. Use simple if/else instead of try/catch.
The poll plugin automatically cleans up when the SDK is destroyed:
// Start polling
const promise = sdk.poll.waitFor(() => false, { timeout: 60000 });
// Destroy SDK (e.g., on page unload)
sdk.destroy();
// All active polls are cancelled
// No memory leaks! ✨This prevents memory leaks from abandoned polls.
The poll plugin is fully typed:
// Type-safe condition
const success: boolean = await sdk.poll.waitFor(() => {
return window.myApp?.ready === true;
});
// Type-safe element
const modal: Element | null = await sdk.poll.element('#modal');
// Type-safe global
const jQuery: unknown = await sdk.poll.global('window.jQuery');
// Type assertion if you know the type
const typedJQuery = await sdk.poll.global('window.jQuery') as JQueryStatic;- All modern browsers (Chrome, Firefox, Safari, Edge)
- Requires Promise support (IE11 not supported)
- DOM methods require browser environment (not Node.js)
- Use generous timeouts for slow networks (10+ seconds)
- Check immediately for performance (
immediate: true) - Handle timeout case gracefully
- Use
element()andglobal()for common cases - Let SDK destroy clean up polls
- Use very short timeouts (<1 second)
- Poll for things that emit events (use events instead)
- Forget to handle timeout case
- Create infinite polls (always use timeout)
- Poll in tight loops (use reasonable intervals)
MIT