主页/jQuery教程/回调、延迟与异步/jQuery异步编程的基石:$.Deferred() 延迟对象

jQuery异步编程的基石:$.Deferred() 延迟对象

6,636字
28–42 分钟

概览

目录

$.Deferred() 是 jQuery 中实现异步编程的核心机制,它提供了一种统一的方式来管理异步操作的状态和回调函数。一个jQuery延迟对象代表一个尚未完成的操作,并允许绑定多个成功、失败或进度通知的回调。自 jQuery 1.5 引入以来,$.Deferred() 极大地改变了处理异步任务(如 Ajax 请求、动画和自定义耗时操作)的方式,为代码带来了更高的可读性和可维护性。本章节将深入解析 jQuery.Deferred() 的工作原理、核心方法以及与现代 Promise 的关联。

创建与理解 Deferred 对象

$.Deferred() 是一个工厂函数,用于创建一个新的 延迟对象。该对象内部维护着操作的状态和回调队列。通过操作这个对象,可以控制异步流程。

语法

var deferred = $.Deferred( [beforeStart] );
  • beforeStart(可选):一个函数,在构造器内部、返回 Deferred 对象之前被调用。该函数接收新创建的 Deferred 对象作为参数,可用于执行一些初始化逻辑。

状态

一个 Deferred 对象 始终处于以下三种状态之一:

  1. pending(待定):初始状态,操作尚未完成,也未失败。
  2. resolved(已解决):操作成功完成,调用了 resolveresolveWith
  3. rejected(已拒绝):操作失败,调用了 rejectrejectWith

一旦状态变为 resolvedrejected,它将被锁定,无法再改变。可以额外添加一个进度通知机制,通过 notifyprogress 回调来报告中间状态,这不影响最终状态的变更。

示例:创建一个基础的 Deferred 对象

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>创建 Deferred 对象</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
    <button id="startTask">启动耗时任务</button>
    <div id="status"></div>

    <script>
        function simulateAsyncTask() {
            var deferred = $.Deferred();
            var timeLeft = 3;

            // 模拟异步操作(例如 setTimeout)
            var intervalId = setInterval(function() {
                timeLeft--;
                deferred.notify('任务进行中,剩余 ' + timeLeft + ' 秒...');

                if (timeLeft === 0) {
                    clearInterval(intervalId);
                    deferred.resolve('任务完成!');
                }
            }, 1000);

            return deferred.promise(); // 返回 Promise 对象,防止外部修改状态
        }

        $('#startTask').on('click', function() {
            var $status = $('#status');
            $status.html('开始任务...');

            var promise = simulateAsyncTask();

            promise.progress(function(message) {
                $status.append('<p>' + message + '</p>');
            }).done(function(message) {
                $status.append('<p><strong>' + message + '</strong></p>');
            }).fail(function(message) {
                $status.append('<p style="color:red;">' + message + '</p>');
            });
        });
    </script>
</body>
</html>

此示例创建了一个 simulateAsyncTask 函数,它返回一个 Promise 对象(通过 deferred.promise() 获得)。外部代码通过 .progress().done() 绑定回调,而无法直接调用 resolvereject,这保证了封装性。这正是 jQuery异步编程 中常用的模式。

核心方法:控制状态

Deferred 对象提供了一系列方法来改变其内部状态,从而触发相应的回调。

.resolve() / .resolveWith()

将 Deferred 对象的状态设置为 resolved,并执行所有已绑定的 done 回调。

语法

deferred.resolve( [args] )
deferred.resolveWith( context [, args] )
  • args:传递给回调函数的参数。
  • context:在回调函数内部 this 指向的对象。

.reject() / .rejectWith()

将 Deferred 对象的状态设置为 rejected,并执行所有已绑定的 fail 回调。

语法

deferred.reject( [args] )
deferred.rejectWith( context [, args] )

.notify() / .notifyWith()

执行所有已绑定的 progress 回调,但不改变 Deferred 对象的最终状态。

语法

deferred.notify( [args] )
deferred.notifyWith( context [, args] )

.promise()

返回一个受限制的 Promise 对象。该对象只包含用于绑定回调的方法(如 donefailprogressthen 等),而不包含修改状态的方法(如 resolvereject)。这是防止外部代码意外改变内部状态的关键。

语法

deferred.promise( [target] )
  • target(可选):一个对象,Promise 的方法将被合并到这个对象上。

添加回调:响应状态变化

通过以下方法可以为 Deferred 对象 绑定回调函数,当状态改变时它们会被自动执行。

方法触发时机描述
.done( callbacks )状态变为 resolved添加一个或多个成功回调。
.fail( callbacks )状态变为 rejected添加一个或多个失败回调。
.progress( callbacks )每次调用 .notify()添加一个或多个进度回调。
.then( doneFilter [, failFilter ] [, progressFilter ] )视情况而定将三个类型的回调合并,并可对结果进行链式过滤。
.always( callbacks )状态变为 resolvedrejected添加一个无论成功还是失败都会执行的回调。

示例:综合使用各种回调

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Deferred 回调示例</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
    <button id="randomTask">执行随机结果的任务</button>
    <div id="output"></div>

    <script>
        function randomAsyncTask() {
            var deferred = $.Deferred();
            var random = Math.random();

            setTimeout(function() {
                if (random > 0.5) {
                    deferred.resolve('成功!随机数大于 0.5');
                } else {
                    deferred.reject('失败!随机数小于等于 0.5');
                }
            }, 1000);

            return deferred.promise();
        }

        $('#randomTask').on('click', function() {
            var $out = $('#output');
            $out.html('等待结果...');

            randomAsyncTask()
                .done(function(msg) {
                    $out.html('<p style="color:green;">成功: ' + msg + '</p>');
                })
                .fail(function(msg) {
                    $out.html('<p style="color:red;">失败: ' + msg + '</p>');
                })
                .always(function() {
                    $out.append('<p><em>任务已结束(always 回调)</em></p>');
                });
        });
    </script>
</body>
</html>

此示例展示了 .done().fail().always() 的用法,清晰体现了 jQuery延迟对象 如何根据异步结果执行不同的逻辑分支。

$.when():管理多个异步任务

$.when() 是一个工具函数,用于协调多个 Deferred 对象(或任何 Promise 对象)的执行。它接受一个或多个 Deferred 对象作为参数,并返回一个新的 Promise 对象

语法

jQuery.when( deferreds )
  • 如果传入单个 Deferred,则返回的 Promise 对象直接反映该 Deferred 的状态。
  • 如果传入多个 Deferred,则返回的 Promise 对象在所有 Deferred 都被 resolved 时变为 resolved,但只要有一个被 rejected,它就立即变为 rejected。其回调函数接收的参数是各个 Deferred 的 resolve 参数组成的数组。

示例:等待多个异步操作完成

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>$.when() 示例</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
    <button id="multiTask">执行两个并行任务</button>
    <div id="result"></div>

    <script>
        function taskA() {
            var d = $.Deferred();
            setTimeout(function() { d.resolve('任务 A 完成'); }, 1500);
            return d.promise();
        }

        function taskB() {
            var d = $.Deferred();
            setTimeout(function() { d.resolve('任务 B 完成'); }, 1000);
            return d.promise();
        }

        $('#multiTask').on('click', function() {
            var $result = $('#result');
            $result.html('等待所有任务...');

            $.when(taskA(), taskB()).done(function(resultA, resultB) {
                $result.html('<p>' + resultA + '</p><p>' + resultB + '</p>');
            }).fail(function() {
                $result.html('<p style="color:red;">至少一个任务失败</p>');
            });
        });
    </script>
</body>
</html>

$.when() 使得处理并行异步任务变得异常简单,是 jQuery异步编程 中不可或缺的工具。

将 Ajax 转化为 Promise

从 jQuery 1.5 开始,$.ajax() 返回的对象(jqXHR)实现了 Promise 接口。这意味着可以像操作 Deferred 一样链式调用 .done().fail().always().then() 来处理 Ajax 请求结果。

示例:使用 Promise 风格的 Ajax

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Ajax 与 Promise 结合</title>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
    <button id="loadData">加载数据</button>
    <div id="content"></div>

    <script>
        $('#loadData').on('click', function() {
            var $content = $('#content');
            $content.html('加载中...');

            $.ajax({
                url: 'https://api.github.com/repos/jquery/jquery',
                dataType: 'json'
            })
            .done(function(data) {
                $content.html('<p>仓库名: ' + data.full_name + '</p><p>星标: ' + data.stargazers_count + '</p>');
            })
            .fail(function(jqXHR, textStatus) {
                $content.html('<p style="color:red;">请求失败: ' + textStatus + '</p>');
            })
            .always(function() {
                $content.append('<p><em>请求结束 (always)</em></p>');
            });
        });
    </script>
</body>
</html>

这种写法避免了传统的回调嵌套,使代码更加扁平化和易于维护。

jQuery 4.0 的变化

在 jQuery 4.0.0 中,官方对 Deferred 和 Callbacks 模块 进行了调整。根据 官方博客 的说明:

  • slim 构建版本 移除了 Deferred 和 Callbacks 模块,使核心库体积减少约 8k(gzipped)。
  • 主要原因是所有支持 jQuery 4.0 的浏览器(除了 IE11)都已原生支持 Promise/A+ 标准。因此,大多数情况下可以使用原生 Promise 替代 Deferred。
  • 对于需要支持 IE11 或使用 Deferred 特有功能(如 .notify() 进度通知)的场景,建议使用完整版的 jQuery 4.0 或引入 polyfill。

这一变化反映了前端社区向标准化、轻量化发展的趋势。了解 $.Deferred() 的原理依然非常重要,因为它不仅有助于维护旧项目,也加深了对异步编程模型的理解。

版本变更记录

版本变更内容
1.5引入 jQuery.Deferred 模块,并重构 Ajax 模块以返回实现 Promise 接口的 jqXHR 对象。
1.6增强 .pipe() 方法(后被 .then() 替代),改进链式调用能力。
1.8.then() 方法的行为被标准化,以更接近 Promise/A+ 规范。
3.0正式弃用 .pipe() 方法,推荐使用 .then()jqXHR.success().error().complete() 被弃用,推荐使用 Promise 风格方法。
4.0slim 构建版本 移除 Deferred 和 Callbacks 模块。完整版本保留。推荐在新项目中使用原生 Promise。

浏览器兼容性

jQuery.Deferred() 的兼容性与 jQuery 核心库保持一致。对于使用完整 jQuery 4.0(包含 Deferred 模块)的项目,其支持范围如下:

浏览器最低支持版本
Chrome30+
Edge12+
Firefox25+
Opera18+
Safari7+
Chrome Android30+
Firefox for Android25+
Opera Android18+
Safari on iOS7+
Samsung Internet4.0+
WebView Android4.4+
WebView on iOS7+

注:对于需要支持 IE 6-8 的遗留项目,必须使用 jQuery 1.x 或 2.x 版本,这些版本中 Deferred 功能可用。jQuery 4.0 完整版仍支持 IE11。

掌握 jQuery.Deferred() 是深入理解 jQuery异步编程 的关键一步。它不仅为处理复杂的异步流程提供了优雅的解决方案,其设计思想也深刻影响了后续的 Promise 标准。即便在现代开发中越来越多地使用原生 Promise,Deferred 对象在 jQuery 生态中依然扮演着重要角色,是连接过去与未来异步编程模式的桥梁。