jQuery延迟对象与Promise

延迟对象(Deferred)和Promise是jQuery处理异步操作的强大工具,可以优雅地管理复杂的异步代码流。
Promise状态流
pending(进行中) resolved(已完成) rejected(已拒绝)

Promise对象代表一个异步操作的最终完成(或失败)及其结果值。

1. Deferred对象

Deferred对象是jQuery的异步任务管理器,可以创建并控制Promise的状态。

Deferred对象演示

基本用法:


// 创建Deferred对象
var deferred = $.Deferred();

// 获取Promise对象
var promise = deferred.promise();

// 异步任务完成后调用resolve
function asyncTask() {
    setTimeout(function() {
        deferred.resolve("任务完成!");
    }, 1000);
}

// 绑定回调函数
promise.done(function(message) {
    console.log("成功:", message);
}).fail(function(error) {
    console.log("失败:", error);
}).always(function() {
    console.log("总是执行");
});

// 开始异步任务
asyncTask();

// 也可以直接创建已完成的Deferred
var resolvedDeferred = $.Deferred().resolve("立即完成");
var rejectedDeferred = $.Deferred().reject("立即拒绝");

// 进度通知
var progressDeferred = $.Deferred();
progressDeferred.progress(function(value) {
    console.log("进度:", value + "%");
});

// 手动触发进度
progressDeferred.notify(50);
                            
Deferred对象方法:
方法 描述 示例
resolve([args]) 将Deferred状态设置为已完成 deferred.resolve(data)
resolveWith(context, [args]) 指定上下文并完成 deferred.resolveWith(this, [data])
reject([args]) 将Deferred状态设置为已拒绝 deferred.reject(error)
rejectWith(context, [args]) 指定上下文并拒绝 deferred.rejectWith(this, [error])
notify([args]) 触发进度回调 deferred.notify(progress)
promise([target]) 返回Promise对象 var promise = deferred.promise()
state() 获取当前状态 deferred.state() // "pending"|"resolved"|"rejected"
注意:Deferred对象的状态一旦确定(resolved或rejected)就不能再改变,这被称为Promise的不可变性。

2. Promise对象

Promise对象是Deferred的只读版本,用于订阅异步操作的结果。


创建Promise

异步操作

完成处理

Promise对象使用:


// 从Deferred获取Promise
var deferred = $.Deferred();
var promise = deferred.promise();

// 添加回调函数
promise.done(function(data) {
    console.log("成功回调:", data);
});

promise.fail(function(error) {
    console.log("失败回调:", error);
});

promise.always(function() {
    console.log("总是执行");
});

promise.progress(function(value) {
    console.log("进度更新:", value);
});

// 链式调用回调
promise
    .done(function(data) {
        console.log("第一步:", data);
        return data + " -> 处理完成";
    })
    .done(function(data) {
        console.log("第二步:", data);
    });

// $.when() 创建Promise
var promise1 = $.Deferred().resolve("结果1").promise();
var promise2 = $.Deferred().resolve("结果2").promise();

$.when(promise1, promise2).done(function(result1, result2) {
    console.log("所有Promise都完成:", result1, result2);
});

// 从现有值创建Promise
var resolvedPromise = $.when("立即完成的值");
resolvedPromise.done(function(value) {
    console.log(value); // "立即完成的值"
});

// Promise状态检查
console.log(promise.state()); // "pending", "resolved", 或 "rejected"
                            
Promise包装异步函数

// 包装AJAX请求
function getUserData(userId) {
    var deferred = $.Deferred();

    $.ajax({
        url: '/api/users/' + userId,
        method: 'GET'
    }).done(function(data) {
        deferred.resolve(data);
    }).fail(function(error) {
        deferred.reject(error);
    });

    return deferred.promise();
}

// 包装setTimeout
function delay(ms) {
    var deferred = $.Deferred();

    setTimeout(function() {
        deferred.resolve("等待了 " + ms + " 毫秒");
    }, ms);

    return deferred.promise();
}

// 包装事件
function onClickPromise(element) {
    var deferred = $.Deferred();

    $(element).one('click', function(event) {
        deferred.resolve({
            event: event,
            element: this
        });
    });

    return deferred.promise();
}

// 使用包装的函数
getUserData(123)
    .done(function(user) {
        console.log("用户数据:", user);
    })
    .fail(function(error) {
        console.error("获取失败:", error);
    });

delay(1000).done(function(message) {
    console.log(message); // "等待了 1000 毫秒"
});

onClickPromise('#myButton').done(function(data) {
    console.log("按钮被点击了:", data.element);
});
                                
Promise方法对比:
方法 Deferred Promise 说明
resolve/reject 只有Deferred可以改变状态
done/fail/always 两者都可以添加回调
promise() Deferred返回Promise对象
state() 两者都可以检查状态

3. 链式调用

Promise支持链式调用,可以优雅地处理多个连续的异步操作。

链式调用演示

链式调用示例:


// 基本链式调用
function asyncStep1() {
    var deferred = $.Deferred();
    setTimeout(function() {
        deferred.resolve("第一步完成");
    }, 1000);
    return deferred.promise();
}

function asyncStep2(data) {
    var deferred = $.Deferred();
    setTimeout(function() {
        deferred.resolve(data + " -> 第二步完成");
    }, 1000);
    return deferred.promise();
}

function asyncStep3(data) {
    var deferred = $.Deferred();
    setTimeout(function() {
        deferred.resolve(data + " -> 第三步完成");
    }, 1000);
    return deferred.promise();
}

// 链式调用
asyncStep1()
    .then(function(result1) {
        console.log(result1);
        return asyncStep2(result1);
    })
    .then(function(result2) {
        console.log(result2);
        return asyncStep3(result2);
    })
    .then(function(result3) {
        console.log("最终结果:", result3);
    })
    .fail(function(error) {
        console.error("链式调用失败:", error);
    });

// 使用pipe方法(jQuery 1.8之前)
asyncStep1()
    .pipe(function(result1) {
        return asyncStep2(result1);
    })
    .pipe(function(result2) {
        return asyncStep3(result2);
    })
    .done(function(finalResult) {
        console.log("pipe最终结果:", finalResult);
    });

// 链式调用中的错误处理
function riskyStep() {
    var deferred = $.Deferred();
    setTimeout(function() {
        if (Math.random() > 0.5) {
            deferred.resolve("成功");
        } else {
            deferred.reject("随机失败");
        }
    }, 500);
    return deferred.promise();
}

riskyStep()
    .then(function(result) {
        console.log("第一步成功:", result);
        return "继续下一步";
    })
    .then(function(result) {
        console.log("第二步:", result);
        throw new Error("手动抛出错误");
    })
    .then(function(result) {
        // 这里不会执行,因为上一步出错了
        console.log("第三步:", result);
    })
    .fail(function(error) {
        console.error("链式调用出错:", error);
    })
    .always(function() {
        console.log("链式调用结束");
    });

// 返回值的链式传递
$.Deferred().resolve(10)
    .then(function(value) {
        console.log("原始值:", value); // 10
        return value * 2; // 返回新值
    })
    .then(function(value) {
        console.log("两倍值:", value); // 20
        return $.Deferred().resolve(value + 5).promise(); // 返回新的Promise
    })
    .then(function(value) {
        console.log("最终值:", value); // 25
    });
                            
链式调用方法对比:
方法 描述 返回值 版本
.then() 添加完成和失败回调,返回新Promise Promise jQuery 1.8+
.pipe() 管道方法,类似then但参数不同 Promise jQuery 1.6-1.7(已弃用)
.done() 添加成功回调,不返回新Promise 原始Promise jQuery 1.5+
.fail() 添加失败回调,不返回新Promise 原始Promise jQuery 1.5+
提示:在jQuery 1.8+中,推荐使用.then()方法进行链式调用,它返回一个新的Promise对象,可以实现真正的链式操作。

4. 并行处理

使用$.when()方法可以并行处理多个异步操作,等待所有操作完成。

并行任务演示
任务1
等待
任务2
等待
任务3
等待

并行处理示例:


// 基本并行处理
function fetchUserData() {
    return $.Deferred().resolve("用户数据").promise();
}

function fetchProductData() {
    return $.Deferred().resolve("产品数据").promise();
}

function fetchOrderData() {
    return $.Deferred().resolve("订单数据").promise();
}

// 等待所有Promise完成
$.when(
    fetchUserData(),
    fetchProductData(),
    fetchOrderData()
).done(function(userData, productData, orderData) {
    console.log("所有数据获取完成:");
    console.log("用户:", userData);
    console.log("产品:", productData);
    console.log("订单:", orderData);

    // 处理所有数据
    processAllData(userData, productData, orderData);
}).fail(function(error) {
    console.error("某个请求失败:", error);
});

// 动态数量的并行请求
function fetchMultipleItems(itemIds) {
    var promises = [];

    itemIds.forEach(function(id) {
        var promise = $.ajax({
            url: '/api/items/' + id,
            method: 'GET'
        });
        promises.push(promise);
    });

    // 使用apply将数组作为参数传递
    return $.when.apply($, promises);
}

fetchMultipleItems([1, 2, 3, 4, 5])
    .done(function() {
        // arguments包含所有请求的结果
        console.log("所有项目获取完成");
        for(var i = 0; i < arguments.length; i++) {
            console.log("项目" + (i+1) + ":", arguments[i]);
        }
    });

// 部分成功处理
function createPartialSuccessHandler() {
    var successes = [];
    var failures = [];

    return {
        done: function(callback) {
            successes.push(callback);
            return this;
        },
        fail: function(callback) {
            failures.push(callback);
            return this;
        },
        execute: function() {
            successes.forEach(function(callback) {
                callback();
            });
            failures.forEach(function(callback) {
                callback();
            });
        }
    };
}

// 使用$.when处理部分失败
var promise1 = $.Deferred().resolve("成功1").promise();
var promise2 = $.Deferred().reject("失败2").promise();
var promise3 = $.Deferred().resolve("成功3").promise();

$.when(promise1, promise2, promise3)
    .done(function() {
        console.log("所有都成功(这里不会执行)");
    })
    .fail(function(error) {
        console.log("有任务失败:", error); // 输出:有任务失败: 失败2
    });

// 并行与串行结合
function complexWorkflow() {
    // 第一步:并行获取数据
    return $.when(
        fetchUserProfile(),
        fetchUserSettings()
    ).then(function(profile, settings) {
        // 第二步:串行处理数据
        return processProfileAndSettings(profile, settings);
    }).then(function(processedData) {
        // 第三步:并行保存到不同地方
        return $.when(
            saveToDatabase(processedData),
            saveToCloud(processedData)
        );
    });
}

// 带超时的并行处理
function fetchWithTimeout(url, timeout) {
    var deferred = $.Deferred();

    // 创建超时Promise
    var timeoutPromise = $.Deferred();
    setTimeout(function() {
        timeoutPromise.reject(new Error("请求超时"));
    }, timeout);

    // 创建请求Promise
    var requestPromise = $.ajax({ url: url });

    // 使用$.when竞争
    $.when(requestPromise, timeoutPromise)
        .done(function(data) {
            deferred.resolve(data);
        })
        .fail(function(error) {
            deferred.reject(error);
        });

    return deferred.promise();
}
                            
并行处理模式:
全部成功模式

// 所有任务都必须成功
$.when(task1, task2, task3)
    .done(function(r1, r2, r3) {
        // 所有任务都成功
    })
    .fail(function(error) {
        // 任意任务失败
    });
                                            
任意成功模式

// 任意任务成功即可
$.Deferred(function(deferred) {
    var success = false;
    $.when(task1, task2, task3)
        .done(function() {
            if (!success) {
                success = true;
                deferred.resolve(arguments);
            }
        })
        .fail(function() {
            // 继续等待其他任务
        });
}).promise();
                                            

5. 错误处理

Promise提供了统一的错误处理机制,可以优雅地捕获和处理异步操作中的异常。

错误处理演示

错误处理示例:


// 基本错误处理
function riskyOperation() {
    var deferred = $.Deferred();

    setTimeout(function() {
        try {
            // 模拟可能出错的操作
            if (Math.random() > 0.7) {
                throw new Error("随机错误");
            }
            deferred.resolve("操作成功");
        } catch (error) {
            deferred.reject(error);
        }
    }, 1000);

    return deferred.promise();
}

riskyOperation()
    .done(function(result) {
        console.log("成功:", result);
    })
    .fail(function(error) {
        console.error("失败:", error.message);
    })
    .always(function() {
        console.log("操作完成(无论成功失败)");
    });

// 链式调用中的错误传播
function step1() {
    var deferred = $.Deferred();
    setTimeout(function() {
        deferred.resolve("第一步");
    }, 500);
    return deferred.promise();
}

function step2(data) {
    var deferred = $.Deferred();
    setTimeout(function() {
        if (data === "第一步") {
            deferred.reject(new Error("第二步失败"));
        } else {
            deferred.resolve(data + " -> 第二步");
        }
    }, 500);
    return deferred.promise();
}

step1()
    .then(step2)
    .then(function(result) {
        console.log("成功:", result);
    })
    .fail(function(error) {
        console.error("链式调用失败:", error.message);
        // 错误会一直传播到第一个fail回调
    });

// 错误恢复(catch和recover)
function fetchData() {
    return $.Deferred().reject(new Error("数据源失败")).promise();
}

function getFallbackData() {
    return $.Deferred().resolve("备用数据").promise();
}

// 尝试主数据源,失败时使用备用数据
fetchData()
    .fail(function(error) {
        console.warn("主数据源失败:", error.message);
        return getFallbackData(); // 返回新的Promise
    })
    .done(function(data) {
        console.log("最终数据:", data); // "备用数据"
    });

// 统一错误处理函数
function handleAPIError(error) {
    console.error("API错误:", error);

    // 根据错误类型采取不同措施
    if (error.status === 401) {
        // 未授权,跳转到登录页
        window.location.href = '/login';
    } else if (error.status === 404) {
        // 资源不存在
        showToast("请求的资源不存在");
    } else if (error.status === 500) {
        // 服务器错误
        showToast("服务器内部错误,请稍后重试");
    } else {
        // 其他错误
        showToast("网络请求失败: " + error.message);
    }

    // 返回一个rejected promise继续传播错误
    return $.Deferred().reject(error).promise();
}

// 使用统一错误处理
$.ajax({
    url: '/api/data',
    method: 'GET'
})
    .fail(handleAPIError)
    .done(function(data) {
        console.log("数据:", data);
    });

// 超时错误处理
function withTimeout(promise, timeoutMs) {
    var deferred = $.Deferred();

    // 创建超时Promise
    var timeoutPromise = $.Deferred();
    setTimeout(function() {
        timeoutPromise.reject(new Error("操作超时 (" + timeoutMs + "ms)"));
    }, timeoutMs);

    // 竞争:请求 vs 超时
    $.when(promise, timeoutPromise)
        .done(function(data) {
            deferred.resolve(data);
        })
        .fail(function(error) {
            deferred.reject(error);
        });

    return deferred.promise();
}

// 使用超时包装
var apiPromise = $.ajax({ url: '/api/slow' });
withTimeout(apiPromise, 5000)
    .done(function(data) {
        console.log("数据:", data);
    })
    .fail(function(error) {
        console.error("失败:", error.message);
    });

// 全局Promise错误处理
$(document).ajaxError(function(event, jqxhr, settings, error) {
    console.error("全局AJAX错误:", error);
});

// 使用always进行清理工作
function loadResource(url) {
    var deferred = $.Deferred();
    var isLoading = true;

    $.ajax({
        url: url,
        method: 'GET'
    })
        .done(function(data) {
            deferred.resolve(data);
        })
        .fail(function(error) {
            deferred.reject(error);
        })
        .always(function() {
            isLoading = false;
            console.log("资源加载完成,清理工作");
        });

    return deferred.promise();
}
                            
最佳实践:
  1. 总是在链式调用的末尾添加.fail()回调处理未捕获的错误
  2. 使用.always()进行清理工作,无论成功失败都会执行
  3. 在适当的地方进行错误恢复,而不是让错误一直传播
  4. 记录详细的错误信息以便调试

6. 与ES6 Promise对比

了解jQuery Deferred/Promise与ES6 Promise的异同,以及如何进行互操作。

特性 jQuery Deferred/Promise ES6 Promise
创建方式 $.Deferred() new Promise()
状态变更 通过resolve()/reject()方法 通过执行器函数参数
链式调用 .then(), .done(), .fail() .then(), .catch()
进度通知 .progress(), .notify() 不支持
并行处理 $.when() Promise.all(), Promise.race()
错误传播 需要显式调用.fail() 自动传播到.catch()
返回值 .then()返回新Promise .then()返回新Promise

互操作示例:


// 将jQuery Promise转换为ES6 Promise
function jQueryToES6Promise(jqPromise) {
    return new Promise(function(resolve, reject) {
        jqPromise
            .done(resolve)
            .fail(reject);
    });
}

// 将ES6 Promise转换为jQuery Promise
function ES6TojQueryPromise(es6Promise) {
    var deferred = $.Deferred();

    es6Promise
        .then(function(value) {
            deferred.resolve(value);
        })
        .catch(function(error) {
            deferred.reject(error);
        });

    return deferred.promise();
}

// 混合使用示例
function mixedPromiseWorkflow() {
    // jQuery Promise
    var jqPromise = $.ajax({
        url: '/api/data',
        method: 'GET'
    });

    // ES6 Promise
    var es6Promise = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve("ES6 Promise完成");
        }, 1000);
    });

    // 转换为统一格式
    var es6FromJQ = jQueryToES6Promise(jqPromise);
    var jqFromES6 = ES6TojQueryPromise(es6Promise);

    // 使用ES6 Promise.all
    return Promise.all([es6FromJQ, es6Promise])
        .then(function(results) {
            console.log("ES6 Promise.all完成:", results);
            return results;
        });
}

// 使用$.when处理混合Promise
function mixedWithWhen() {
    var jqPromise = $.Deferred().resolve("jQuery结果").promise();
    var es6Promise = Promise.resolve("ES6结果");

    // 将ES6 Promise转换为jQuery Promise
    var jqFromES6 = ES6TojQueryPromise(es6Promise);

    return $.when(jqPromise, jqFromES6)
        .done(function(result1, result2) {
            console.log("两个都完成:", result1, result2);
        });
}

// 现代化转换函数
function adaptToES6(jqPromise) {
    // 如果已经是ES6 Promise,直接返回
    if (jqPromise instanceof Promise) {
        return jqPromise;
    }

    // 转换jQuery Promise
    return new Promise(function(resolve, reject) {
        if (jqPromise.state && jqPromise.state() === 'resolved') {
            resolve(jqPromise.result);
        } else if (jqPromise.state && jqPromise.state() === 'rejected') {
            reject(jqPromise.result);
        } else {
            jqPromise.then(resolve, reject);
        }
    });
}

// 使用示例
var jqDeferred = $.Deferred();
setTimeout(function() {
    jqDeferred.resolve("jQuery延迟对象");
}, 500);

adaptToES6(jqDeferred.promise())
    .then(function(result) {
        console.log("转换为ES6 Promise:", result);
    })
    .catch(function(error) {
        console.error("错误:", error);
    });

// 批量转换
function convertAllToES6(promises) {
    return promises.map(function(promise) {
        if (promise.then && promise.done && promise.fail) {
            // jQuery Promise
            return adaptToES6(promise);
        } else if (promise instanceof Promise) {
            // 已经是ES6 Promise
            return promise;
        } else {
            // 其他值,包装为ES6 Promise
            return Promise.resolve(promise);
        }
    });
}

// 现代化建议
function modernAsyncWorkflow() {
    // 如果支持ES6 Promise,优先使用
    if (typeof Promise !== 'undefined') {
        return new Promise(function(resolve, reject) {
            // 异步操作
            setTimeout(function() {
                resolve("现代Promise");
            }, 1000);
        });
    } else {
        // 回退到jQuery Promise
        var deferred = $.Deferred();
        setTimeout(function() {
            deferred.resolve("jQuery Promise");
        }, 1000);
        return deferred.promise();
    }
}
                            
迁移建议:
  1. 新项目:优先使用ES6 Promise或async/await
  2. 老项目:逐步将.done()/.fail()替换为.then()
  3. 混合环境:使用适配器函数进行转换
  4. 进度通知:如果需要进度通知,使用jQuery Promise
  5. 并行处理:优先使用Promise.all()Promise.race()
未来趋势:async/await

// 使用async/await处理jQuery Promise
async function fetchDataWithJQuery() {
    try {
        // jQuery AJAX返回的是jQuery Promise
        const data = await $.ajax({
            url: '/api/data',
            method: 'GET'
        });

        console.log("数据:", data);
        return data;
    } catch (error) {
        console.error("获取失败:", error);
        throw error;
    }
}

// 将jQuery Deferred转换为async/await可用
function createAsyncDeferred() {
    const deferred = $.Deferred();

    // 为Deferred添加async支持
    deferred.async = async function() {
        return new Promise((resolve, reject) => {
            this.then(resolve, reject);
        });
    };

    return deferred;
}

// 使用示例
async function example() {
    const deferred = createAsyncDeferred();

    setTimeout(() => {
        deferred.resolve("异步结果");
    }, 1000);

    const result = await deferred.async();
    console.log("结果:", result);
}
                            

综合实践:天气预报应用

使用Promise链式调用实现一个天气预报应用。

功能要求
  1. 根据IP获取地理位置
  2. 根据位置获取天气数据
  3. 获取未来5天预报
  4. 错误处理和降级方案
  5. 数据缓存和超时控制
Promise链设计

获取位置

当前天气

天气预报

数据显示
关键概念总结
概念 描述 示例方法 使用场景
Deferred 可写的Promise,可以改变状态 resolve(), reject() 创建自定义异步操作
Promise 只读的异步结果容器 done(), fail() 订阅异步操作结果
链式调用 多个异步操作顺序执行 then() 依赖前一步结果的异步流程
并行处理 多个异步操作同时执行 $.when() 不相关的多个异步任务
错误传播 错误沿Promise链传递 fail() 统一错误处理
进度通知 异步操作进度更新 progress() 长时间操作的进度反馈