Promise-based utilities for timing control, blocking, and promise detection in async workflows with support for various timeout scenarios and promise identification.
Returns a Promise that resolves after the requested timeout with optional return value and custom setTimeout support.
/**
* Returns a Promise that resolves after the requested timeout
* @param timeout - The number of milliseconds to wait before resolving the Promise
* @param returnValue - The value that the Promise will resolve to
* @param options - Optional settings with custom setTimeout implementation
* @returns A Promise that resolves with returnValue after the specified timeout
*/
function wait<T>(timeout?: number, returnValue?: T, options?: wait.Options): Promise<T>;
namespace wait {
interface Options {
/** Custom setTimeout function to use instead of global setTimeout */
readonly setTimeout?: (callback: () => void, delay: number) => any;
}
}Usage Examples:
import { wait } from "@hapi/hoek";
// Basic delay
await wait(1000); // Wait for 1 second
console.log('1 second has passed');
// Wait with return value
const result = await wait(2000, 'completed');
console.log(result); // 'completed' after 2 seconds
// Use in async workflows
async function processWithDelay() {
console.log('Starting process...');
await wait(500);
console.log('Step 1 complete');
await wait(1000);
console.log('Step 2 complete');
return 'Process finished';
}
// Timeout for rate limiting
async function rateLimitedAPI() {
const results = [];
for (let i = 0; i < 5; i++) {
const data = await fetchData(i);
results.push(data);
await wait(200); // 200ms between requests
}
return results;
}
// Retry with exponential backoff
async function retryWithBackoff(operation: () => Promise<any>, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms...`);
await wait(delay);
}
}
}
// Simulating slow operations for testing
async function simulateSlowAPI() {
await wait(Math.random() * 1000, 'API response');
return { data: 'response data' };
}
// Timeout with specific return values
const timeoutResult = await wait(5000, { status: 'timeout', code: 408 });
// BigInt timeout handling
const bigIntTimeout = 1000n;
await wait(bigIntTimeout); // Automatically converted to number
// Infinite timeout (never resolves)
const neverResolving = wait(Infinity); // Promise that never resolves
// Very large timeout handling
const largeTimeout = Number.MAX_SAFE_INTEGER;
await wait(largeTimeout); // Handled safely with internal chunking
// Custom setTimeout function (for testing or special environments)
const customWait = (timeout: number, returnValue: any, options: any) => {
return wait(timeout, returnValue, {
setTimeout: (callback: Function, delay: number) => {
// Custom timeout implementation
return global.setTimeout(callback, delay);
}
});
};Returns a Promise that never resolves, useful for creating permanent blocking conditions.
/**
* Returns a Promise that never resolves
* @returns A Promise that never resolves (blocks forever)
*/
function block(): Promise<void>;Usage Examples:
import { block } from "@hapi/hoek";
// Create a permanent block
async function waitForever() {
console.log('Starting infinite wait...');
await block(); // This never resolves
console.log('This line never executes');
}
// Use in conditional blocking
async function conditionalBlock(shouldBlock: boolean) {
if (shouldBlock) {
await block(); // Permanently blocks execution
}
return 'execution continued';
}
// Server shutdown prevention
async function keepServerAlive() {
console.log('Server is running...');
await block(); // Keeps the process alive indefinitely
}
// Testing timeout behaviors
async function testTimeout() {
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timeout')), 5000);
});
try {
await Promise.race([block(), timeoutPromise]);
} catch (error) {
console.log('Timeout occurred as expected');
}
}
// Creating permanent background tasks
async function backgroundWorker() {
setInterval(() => {
console.log('Background task running...');
}, 1000);
await block(); // Keep the worker alive forever
}
// Process lifecycle management
async function gracefulShutdown() {
process.on('SIGTERM', () => {
console.log('Received SIGTERM, shutting down...');
process.exit(0);
});
console.log('Application started');
await block(); // Keep process alive until signal received
}
// Development server that never stops
async function devServer() {
console.log('Development server starting on port 3000...');
// Start server logic here
await block(); // Keep server running indefinitely
}Determines if an object is a promise by checking for thenable interface.
/**
* Determines if an object is a promise
* @param promise - The object being tested
* @returns true if the object is a promise, otherwise false
*/
function isPromise(promise: any): boolean;Usage Examples:
import { isPromise } from "@hapi/hoek";
// Basic promise detection
const regularPromise = Promise.resolve('value');
const notPromise = 'string';
console.log(isPromise(regularPromise)); // true
console.log(isPromise(notPromise)); // false
// Async function detection
async function asyncFunction() {
return 'async result';
}
const asyncResult = asyncFunction(); // This is a Promise
console.log(isPromise(asyncResult)); // true
// Thenable object detection
const thenable = {
then: (resolve: Function, reject: Function) => {
resolve('thenable result');
}
};
console.log(isPromise(thenable)); // true
// Function that handles both sync and async values
function processValue(value: any) {
if (isPromise(value)) {
return value.then((result: any) => {
console.log('Async result:', result);
return result;
});
} else {
console.log('Sync result:', value);
return value;
}
}
// Usage with various value types
processValue('immediate value');
processValue(Promise.resolve('promise value'));
processValue(asyncFunction());
// Conditional awaiting
async function conditionalAwait(maybePromise: any) {
if (isPromise(maybePromise)) {
const result = await maybePromise;
return `Awaited: ${result}`;
} else {
return `Immediate: ${maybePromise}`;
}
}
// API response handling
function handleAPIResponse(response: any) {
if (isPromise(response)) {
// Handle async API response
return response.then(data => ({ async: true, data }));
} else {
// Handle sync/cached response
return { async: false, data: response };
}
}
// Utility for mixed return types
class DataProcessor {
process(input: any): any {
const result = this.processSync(input);
if (isPromise(result)) {
return result.then(data => this.postProcess(data));
} else {
return this.postProcess(result);
}
}
private processSync(input: any): any {
// May return sync value or Promise based on input
if (input.needsAsync) {
return fetch('/api/data').then(r => r.json());
}
return { processed: input };
}
private postProcess(data: any) {
return { ...data, timestamp: Date.now() };
}
}
// Testing utilities
function createMockResponse(shouldBeAsync: boolean) {
if (shouldBeAsync) {
return Promise.resolve({ mocked: true });
}
return { mocked: true };
}
// Type checking with validation
function validatePromise(value: any): Promise<any> {
if (!isPromise(value)) {
throw new Error('Expected a Promise');
}
return value;
}
// Error handling for mixed types
async function safeDifficultProcess(value: any) {
try {
if (isPromise(value)) {
return await value;
}
return value;
} catch (error) {
console.error('Error processing value:', error);
return null;
}
}Important Notes:
wait handles various edge cases including BigInt timeouts, infinite timeouts, and very large timeout valueswait prevents JavaScript timer limitations for very long timeoutsblock is useful for keeping processes alive or creating permanent blocking conditions in specific scenariosisPromise checks for thenable interface (objects with then method) rather than just Promise instances