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.
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>;
}// 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');// 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);// 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 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);// 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);// 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}`);
}// 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');
});// 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 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);// 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));// 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));// 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.