or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

ajax.mdcore-selection.mdcss-styling.mddeferred-promises.mdeffects.mdevents.mdindex.mdmanipulation.mdutilities.md
tile.json

deferred-promises.mddocs/

Deferred Objects and Promises

jQuery's Deferred objects provide a powerful Promise-like implementation for managing asynchronous operations. They offer comprehensive control over asynchronous workflows with type-safe callback management and chainable operations.

Deferred Object Interface

Core Deferred Types

declare namespace JQuery {
    // Main Deferred interface
    interface Deferred<T = any, U = any, V = any> {
        // State methods
        state(): "pending" | "resolved" | "rejected";
        
        // Resolution methods
        resolve(value?: T, ...args: any[]): this;
        resolveWith(context: any, value?: T, ...args: any[]): this;
        
        // Rejection methods  
        reject(reason?: U, ...args: any[]): this;
        rejectWith(context: any, reason?: U, ...args: any[]): this;
        
        // Notification methods
        notify(value?: V, ...args: any[]): this;
        notifyWith(context: any, value?: V, ...args: any[]): this;
        
        // Callback registration
        done(doneCallback: (value?: T, ...args: any[]) => void, ...doneCallbacks: Array<(value?: T, ...args: any[]) => void>): this;
        fail(failCallback: (reason?: U, ...args: any[]) => void, ...failCallbacks: Array<(reason?: U, ...args: any[]) => void>): this;
        always(alwaysCallback: (valueOrReason?: T | U, ...args: any[]) => void, ...alwaysCallbacks: Array<(valueOrReason?: T | U, ...args: any[]) => void>): this;
        progress(progressCallback: (value?: V, ...args: any[]) => void, ...progressCallbacks: Array<(value?: V, ...args: any[]) => void>): this;
        
        // Promise conversion
        promise(): Promise<T>;
        promise(target: any): any;
        
        // Pipe methods (deprecated, use then)
        pipe<TResult1 = T, TResult2 = never, TResult3 = never>(
            doneFilter?: (value: T, ...args: any[]) => TResult1 | PromiseLike<TResult1>,
            failFilter?: (reason: U, ...args: any[]) => TResult2 | PromiseLike<TResult2>,
            progressFilter?: (value: V, ...args: any[]) => TResult3 | PromiseLike<TResult3>
        ): Deferred<TResult1, TResult2, TResult3>;
        
        // Then method (Promise-compatible)
        then<TResult1 = T, TResult2 = never>(
            doneFilter?: (value: T, ...args: any[]) => TResult1 | PromiseLike<TResult1>,
            failFilter?: (reason: U, ...args: any[]) => TResult2 | PromiseLike<TResult2>,
            progressFilter?: (value: V, ...args: any[]) => void
        ): Promise<TResult1 | TResult2>;
    }
    
    // Promise interface (read-only view of Deferred)
    interface Promise<T = any, U = any, V = any> {
        state(): "pending" | "resolved" | "rejected";
        
        done(doneCallback: (value?: T, ...args: any[]) => void, ...doneCallbacks: Array<(value?: T, ...args: any[]) => void>): this;
        fail(failCallback: (reason?: U, ...args: any[]) => void, ...failCallbacks: Array<(reason?: U, ...args: any[]) => void>): this;
        always(alwaysCallback: (valueOrReason?: T | U, ...args: any[]) => void, ...alwaysCallbacks: Array<(valueOrReason?: T | U, ...args: any[]) => void>): this;
        progress(progressCallback: (value?: V, ...args: any[]) => void, ...progressCallbacks: Array<(value?: V, ...args: any[]) => void>): this;
        
        then<TResult1 = T, TResult2 = never>(
            doneFilter?: (value: T, ...args: any[]) => TResult1 | PromiseLike<TResult1>,
            failFilter?: (reason: U, ...args: any[]) => TResult2 | PromiseLike<TResult2>,
            progressFilter?: (value: V, ...args: any[]) => void
        ): Promise<TResult1 | TResult2>;
        
        catch<TResult = never>(
            onRejected?: (reason: U) => TResult | PromiseLike<TResult>
        ): Promise<T | TResult>;
        
        finally(onFinally?: () => void): Promise<T>;
    }
}

interface JQueryStatic {
    // Create Deferred objects
    Deferred<T = any, U = any, V = any>(beforeStart?: (deferred: JQuery.Deferred<T, U, V>) => void): JQuery.Deferred<T, U, V>;
    
    // Combine multiple promises/deferreds
    when<T>(...deferreds: Array<T | JQuery.Promise<T> | JQuery.Deferred<T>>): JQuery.Promise<T>;
}

Creating and Using Deferred Objects

Basic Deferred Creation

// Create a simple Deferred
const deferred = $.Deferred<string>();

// Attach callbacks
deferred.done(function(data) {
    console.log('Success:', data);
});

deferred.fail(function(error) {
    console.log('Error:', error);  
});

deferred.always(function(result) {
    console.log('Completed with:', result);
});

// Resolve the Deferred
deferred.resolve('Operation completed successfully');

// Or reject it
// deferred.reject('Operation failed');

Type-Safe Deferred Operations

// Create type-safe Deferred for user data
interface User {
    id: number;
    name: string;
    email: string;
}

interface UserError {
    code: number;
    message: string;
}

const userDeferred = $.Deferred<User, UserError>();

userDeferred.done((user: User) => {
    console.log(`User loaded: ${user.name} (${user.email})`);
});

userDeferred.fail((error: UserError) => {
    console.error(`Failed to load user: ${error.message} (Code: ${error.code})`);
});

// Simulate async user loading
setTimeout(() => {
    if (Math.random() > 0.5) {
        userDeferred.resolve({
            id: 1,
            name: 'John Doe',
            email: 'john@example.com'
        });
    } else {
        userDeferred.reject({
            code: 404,
            message: 'User not found'
        });
    }
}, 1000);

Progress Notifications

// Deferred with progress tracking
const uploadDeferred = $.Deferred<string, string, number>();

uploadDeferred.progress((percentComplete: number) => {
    console.log(`Upload progress: ${percentComplete}%`);
    updateProgressBar(percentComplete);
});

uploadDeferred.done((result: string) => {
    console.log('Upload complete:', result);
});

uploadDeferred.fail((error: string) => {
    console.error('Upload failed:', error);
});

// Simulate file upload with progress
function simulateUpload() {
    let progress = 0;
    const interval = setInterval(() => {
        progress += 10;
        uploadDeferred.notify(progress);
        
        if (progress >= 100) {
            clearInterval(interval);
            uploadDeferred.resolve('File uploaded successfully');
        }
    }, 200);
    
    return uploadDeferred.promise();
}

function updateProgressBar(percent: number) {
    $('#progress-bar').css('width', percent + '%');
}

simulateUpload();

Promise-Style Chaining

Then and Catch Methods

// Promise-style chaining with then/catch
const dataDeferred = $.Deferred<any>();

dataDeferred.promise()
    .then(data => {
        console.log('Processing data:', data);
        return processData(data);
    })
    .then(processedData => {
        console.log('Data processed:', processedData);
        return saveData(processedData);
    })
    .catch(error => {
        console.error('Error in chain:', error);
    })
    .finally(() => {
        console.log('Operation completed');
    });

function processData(data: any): Promise<any> {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ ...data, processed: true });
        }, 500);
    });
}

function saveData(data: any): Promise<string> {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (data.processed) {
                resolve('Data saved successfully');
            } else {
                reject('Cannot save unprocessed data');
            }
        }, 300);
    });
}

// Trigger the chain
setTimeout(() => {
    dataDeferred.resolve({ id: 1, value: 'test data' });
}, 100);

Transformation with Pipe/Then

// Transform Deferred results
interface ApiResponse {
    status: 'success' | 'error';
    data?: any;
    message?: string;
}

const apiDeferred = $.Deferred<ApiResponse>();

// Transform response data
const transformedPromise = apiDeferred.then(
    (response: ApiResponse) => {
        if (response.status === 'success') {
            return response.data;
        } else {
            throw new Error(response.message || 'API Error');
        }
    },
    (error: any) => {
        console.error('API call failed:', error);
        throw error;
    }
);

transformedPromise
    .then(data => {
        console.log('Transformed data:', data);
    })
    .catch(error => {
        console.error('Final error:', error.message);
    });

// Simulate API response
setTimeout(() => {
    apiDeferred.resolve({
        status: 'success',
        data: { users: ['John', 'Jane'], total: 2 }
    });
}, 1000);

Combining Multiple Promises

Using $.when() for Coordination

// Combine multiple async operations
function fetchUser(id: number): JQuery.Promise<User> {
    const deferred = $.Deferred<User>();
    
    setTimeout(() => {
        deferred.resolve({
            id: id,
            name: `User ${id}`,
            email: `user${id}@example.com`
        });
    }, Math.random() * 1000 + 500);
    
    return deferred.promise();
}

function fetchUserPosts(userId: number): JQuery.Promise<any[]> {
    const deferred = $.Deferred<any[]>();
    
    setTimeout(() => {
        deferred.resolve([
            { id: 1, title: 'Post 1', userId },
            { id: 2, title: 'Post 2', userId }
        ]);
    }, Math.random() * 800 + 300);
    
    return deferred.promise();
}

function fetchUserProfile(userId: number): JQuery.Promise<any> {
    const deferred = $.Deferred<any>();
    
    setTimeout(() => {
        deferred.resolve({
            avatar: `avatar${userId}.jpg`,
            bio: `Bio for user ${userId}`,
            joinDate: new Date().toISOString()
        });
    }, Math.random() * 600 + 400);
    
    return deferred.promise();
}

// Wait for all operations to complete
const userId = 123;
$.when(
    fetchUser(userId),
    fetchUserPosts(userId), 
    fetchUserProfile(userId)
).done(function(user, posts, profile) {
    console.log('All data loaded:', {
        user: user,
        posts: posts,
        profile: profile
    });
    
    renderUserPage(user, posts, profile);
}).fail(function() {
    console.error('One or more operations failed');
});

function renderUserPage(user: User, posts: any[], profile: any) {
    console.log(`Rendering page for ${user.name}`);
    console.log(`Posts: ${posts.length}`);
    console.log(`Profile: ${profile.bio}`);
}

Advanced Promise Coordination

// Custom promise coordination utilities
$.extend($, {
    // Wait for all promises, collect results and errors
    allSettled: function<T>(promises: Array<JQuery.Promise<T>>): JQuery.Promise<Array<{status: 'fulfilled' | 'rejected', value?: T, reason?: any}>> {
        const deferred = $.Deferred<Array<{status: 'fulfilled' | 'rejected', value?: T, reason?: any}>>();
        const results: Array<{status: 'fulfilled' | 'rejected', value?: T, reason?: any}> = [];
        let completed = 0;
        
        if (promises.length === 0) {
            deferred.resolve([]);
            return deferred.promise();
        }
        
        $.each(promises, function(index, promise) {
            promise.done(function(value: T) {
                results[index] = { status: 'fulfilled', value: value };
            }).fail(function(reason: any) {
                results[index] = { status: 'rejected', reason: reason };
            }).always(function() {
                completed++;
                if (completed === promises.length) {
                    deferred.resolve(results);
                }
            });
        });
        
        return deferred.promise();
    },
    
    // Race multiple promises (first to resolve/reject wins)
    race: function<T>(promises: Array<JQuery.Promise<T>>): JQuery.Promise<T> {
        const deferred = $.Deferred<T>();
        
        $.each(promises, function(index, promise) {
            promise.done(function(value: T) {
                deferred.resolve(value);
            }).fail(function(reason: any) {
                deferred.reject(reason);
            });
        });
        
        return deferred.promise();
    },
    
    // Delay execution
    delay: function(ms: number): JQuery.Promise<void> {
        const deferred = $.Deferred<void>();
        setTimeout(() => deferred.resolve(), ms);
        return deferred.promise();
    }
});

// Usage of custom promise utilities
const promises = [
    fetchUser(1),
    fetchUser(2),
    fetchUser(3)
];

// Wait for all to settle (some may fail)
$.allSettled(promises).done(function(results) {
    console.log('All operations completed:');
    $.each(results, function(index, result) {
        if (result.status === 'fulfilled') {
            console.log(`User ${index + 1}:`, result.value);
        } else {
            console.log(`User ${index + 1} failed:`, result.reason);
        }
    });
});

// Race condition - first to complete wins
$.race(promises).done(function(firstUser) {
    console.log('First user loaded:', firstUser);
});

// Delayed execution
$.delay(2000).done(function() {
    console.log('This runs after 2 seconds');
});

Error Handling and Recovery

Robust Error Management

// Comprehensive error handling patterns
function robustApiCall(url: string): JQuery.Promise<any> {
    const deferred = $.Deferred<any>();
    
    // Retry logic
    let attempts = 0;
    const maxAttempts = 3;
    
    function tryRequest() {
        attempts++;
        
        $.ajax({
            url: url,
            timeout: 5000
        }).done(function(data) {
            deferred.resolve(data);
        }).fail(function(xhr, status, error) {
            if (attempts < maxAttempts && (xhr.status >= 500 || status === 'timeout')) {
                console.log(`Attempt ${attempts} failed, retrying...`);
                setTimeout(tryRequest, 1000 * attempts); // Exponential backoff
            } else {
                deferred.reject({
                    status: xhr.status,
                    message: error,
                    attempts: attempts
                });
            }
        });
    }
    
    tryRequest();
    return deferred.promise();
}

// Error recovery chain
robustApiCall('/api/data')
    .then(data => {
        console.log('Data loaded successfully:', data);
        return data;
    })
    .catch(error => {
        console.warn('API call failed, trying fallback:', error.message);
        return robustApiCall('/api/fallback');
    })
    .catch(error => {
        console.error('Both primary and fallback failed:', error);
        return { fallback: true, data: [] }; // Default data
    })
    .then(finalData => {
        console.log('Using data:', finalData);
        renderData(finalData);
    });

function renderData(data: any) {
    if (data.fallback) {
        console.log('Rendering with fallback data');
    } else {
        console.log('Rendering with real data');
    }
}

Timeout and Cancellation

// Timeout wrapper for Deferred objects
function withTimeout<T>(deferred: JQuery.Deferred<T>, ms: number): JQuery.Promise<T> {
    const timeoutDeferred = $.Deferred<T>();
    let completed = false;
    
    // Set up timeout
    const timeoutId = setTimeout(() => {
        if (!completed) {
            completed = true;
            timeoutDeferred.reject(new Error(`Operation timed out after ${ms}ms`));
        }
    }, ms);
    
    // Forward original deferred result
    deferred.done(function(result: T) {
        if (!completed) {
            completed = true;
            clearTimeout(timeoutId);
            timeoutDeferred.resolve(result);
        }
    }).fail(function(error: any) {
        if (!completed) {
            completed = true;
            clearTimeout(timeoutId);
            timeoutDeferred.reject(error);
        }
    });
    
    return timeoutDeferred.promise();
}

// Cancellable operation
function cancellableOperation(): { promise: JQuery.Promise<string>, cancel: () => void } {
    const deferred = $.Deferred<string>();
    let cancelled = false;
    
    // Simulate long-running operation
    const timeoutId = setTimeout(() => {
        if (!cancelled) {
            deferred.resolve('Operation completed');
        }
    }, 3000);
    
    return {
        promise: deferred.promise(),
        cancel: () => {
            cancelled = true;
            clearTimeout(timeoutId);
            deferred.reject(new Error('Operation cancelled'));
        }
    };
}

// Usage
const operation = cancellableOperation();
const timedOperation = withTimeout(operation.promise, 2000);

timedOperation
    .done(result => console.log('Success:', result))
    .fail(error => console.log('Failed:', error.message));

// Cancel after 1 second
setTimeout(() => {
    operation.cancel();
}, 1000);

Advanced Patterns

Queue Management with Deferreds

// Sequential execution queue
class SequentialQueue {
    private queue: Array<() => JQuery.Promise<any>> = [];
    private running = false;
    
    add<T>(task: () => JQuery.Promise<T>): JQuery.Promise<T> {
        const deferred = $.Deferred<T>();
        
        this.queue.push(() => {
            return task().done((result: T) => {
                deferred.resolve(result);
            }).fail((error: any) => {
                deferred.reject(error);
            });
        });
        
        this.process();
        return deferred.promise();
    }
    
    private process(): void {
        if (this.running || this.queue.length === 0) return;
        
        this.running = true;
        const task = this.queue.shift()!;
        
        task().always(() => {
            this.running = false;
            this.process(); // Process next task
        });
    }
}

// Usage
const queue = new SequentialQueue();

// Add tasks that will execute sequentially
queue.add(() => {
    console.log('Task 1 started');
    return $.delay(1000).then(() => 'Task 1 completed');
}).done(result => console.log(result));

queue.add(() => {
    console.log('Task 2 started');
    return $.delay(500).then(() => 'Task 2 completed');
}).done(result => console.log(result));

queue.add(() => {
    console.log('Task 3 started');
    return $.delay(800).then(() => 'Task 3 completed');
}).done(result => console.log(result));

Deferred Factory Patterns

// Factory for creating configured Deferred objects
class DeferredFactory {
    static createWithRetry<T>(
        operation: () => JQuery.Promise<T>,
        maxRetries = 3,
        delay = 1000
    ): JQuery.Promise<T> {
        const deferred = $.Deferred<T>();
        let attempts = 0;
        
        function attempt() {
            attempts++;
            operation()
                .done((result: T) => {
                    deferred.resolve(result);
                })
                .fail((error: any) => {
                    if (attempts < maxRetries) {
                        console.log(`Attempt ${attempts} failed, retrying in ${delay}ms...`);
                        setTimeout(attempt, delay);
                    } else {
                        deferred.reject(error);
                    }
                });
        }
        
        attempt();
        return deferred.promise();
    }
    
    static createBatched<T>(
        operations: Array<() => JQuery.Promise<T>>,
        batchSize = 3
    ): JQuery.Promise<T[]> {
        const deferred = $.Deferred<T[]>();
        const results: T[] = [];
        let processedCount = 0;
        
        function processBatch(startIndex: number) {
            const batch = operations.slice(startIndex, startIndex + batchSize);
            const batchPromises = batch.map(op => op());
            
            $.when(...batchPromises).done(function(...batchResults: T[]) {
                results.push(...batchResults);
                processedCount += batch.length;
                
                if (processedCount >= operations.length) {
                    deferred.resolve(results);
                } else {
                    processBatch(startIndex + batchSize);
                }
            }).fail((error: any) => {
                deferred.reject(error);
            });
        }
        
        processBatch(0);
        return deferred.promise();
    }
}

// Usage
const retryableOperation = () => {
    return $.ajax('/api/unreliable').promise();
};

DeferredFactory.createWithRetry(retryableOperation, 5, 500)
    .done(result => console.log('Operation succeeded:', result))
    .fail(error => console.log('Operation failed after retries:', error));

// Batched operations
const operations = Array.from({ length: 10 }, (_, i) => () => {
    return $.delay(Math.random() * 1000).then(() => `Result ${i + 1}`);
});

DeferredFactory.createBatched(operations, 3)
    .done(results => console.log('All operations completed:', results));

Integration with Modern JavaScript

Converting to Native Promises

// Convert jQuery Promise to native Promise
function toNativePromise<T>(jqPromise: JQuery.Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
        jqPromise.done(resolve).fail(reject);
    });
}

// Convert native Promise to jQuery Deferred
function toJQueryDeferred<T>(nativePromise: Promise<T>): JQuery.Deferred<T> {
    const deferred = $.Deferred<T>();
    
    nativePromise
        .then(result => deferred.resolve(result))
        .catch(error => deferred.reject(error));
    
    return deferred;
}

// Usage
const jqPromise = $.ajax('/api/data').promise();
const nativePromise = toNativePromise(jqPromise);

nativePromise
    .then(data => console.log('Native promise result:', data))
    .catch(error => console.error('Native promise error:', error));

// Async/await compatibility
async function fetchDataWithAsyncAwait() {
    try {
        const jqPromise = $.ajax('/api/user/1').promise();
        const userData = await toNativePromise(jqPromise);
        console.log('User data via async/await:', userData);
        return userData;
    } catch (error) {
        console.error('Error in async/await:', error);
        throw error;
    }
}

fetchDataWithAsyncAwait();

The Deferred objects and Promise system provide a powerful foundation for managing asynchronous operations with comprehensive callback handling, error management, and coordination capabilities. While modern JavaScript has native Promises, jQuery's Deferred objects offer additional features like progress notifications and comprehensive callback systems that remain valuable for complex asynchronous workflows.