jQuery deferred.progress() 方法(深入浅出)

什么是 jQuery deferred.progress() 方法

在前端开发中,异步操作是家常便饭。无论是加载图片、请求数据,还是执行复杂的动画,我们都需要处理“等一等”的场景。jQuery 提供了一套优雅的异步处理机制,其中 deferred 对象就是核心工具之一。而 deferred.progress() 方法,正是这套机制中一个容易被忽视却非常实用的功能。

想象一下你正在下载一个大文件。浏览器不会立刻告诉你“完成了”,而是会持续告诉你“已经下载了 30%”。这个“进度提示”就是 progress() 方法的用武之地。它允许你在异步任务进行中,实时接收中间状态信息,而不是只能等到最终成功或失败。

jQuery deferred.progress() 方法用于注册一个或多个回调函数,这些函数会在异步操作的“进度更新”事件触发时被调用。它特别适合需要显示加载进度、执行长时间任务的场景。与 done()fail() 不同,progress() 是在任务“进行中”时被触发,而不是在结束时。

这个方法的出现,让 jQuery 的异步编程能力从“要么成功,要么失败”进化到了“正在进行中,还有多少进度”。对于提升用户体验来说,这一步非常关键。

如何使用 deferred.progress() 方法

使用 deferred.progress() 方法其实非常直观。它接收一个函数作为参数,这个函数会在每次进度更新时被调用。这个函数会接收到一个表示进度的参数,通常是 0 到 1 之间的数字,代表完成比例。

下面是一个基础示例:

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

// 注册 progress 回调
deferred.progress(function(percentage) {
  console.log('当前进度:' + (percentage * 100).toFixed(2) + '%');
});

// 模拟异步任务,每隔 100 毫秒更新一次进度
var interval = setInterval(function() {
  var progress = deferred.state() === 'pending' ? Math.random() : 1;
  deferred.notify(progress);

  if (progress === 1) {
    clearInterval(interval);
    deferred.resolve(); // 任务完成
  }
}, 100);

这段代码中:

  • $.Deferred() 创建了一个异步任务容器,相当于一个“任务管理器”。
  • deferred.progress() 注册了一个回调函数,用于接收进度信息。
  • deferred.notify() 是触发进度更新的关键方法,它会调用所有已注册的 progress 回调。
  • Math.random() 模拟了真实任务中不确定的进度增长。
  • clearIntervaldeferred.resolve() 用于在任务完成后终止模拟并标记完成。

注意:notify() 是触发 progress() 回调的唯一方式。没有 notify()progress() 就不会被调用。

与 done 和 fail 的区别与协作

在理解 deferred.progress() 之前,我们先回顾一下 done()fail() 的作用。done() 在任务成功时被调用,fail() 在任务失败时被调用。它们是“终点”的回调。

progress() 是“途中”的回调。三者共同构成了完整的异步流程控制:

  • done():任务成功完成,执行最终操作。
  • fail():任务失败,执行错误处理。
  • progress():任务进行中,提供实时反馈。

这种分工让代码逻辑更清晰。例如在上传文件的场景中:

var uploadDeferred = $.Deferred();

// 进度回调:更新进度条
uploadDeferred.progress(function(percentage) {
  $('#progress-bar').css('width', (percentage * 100) + '%');
  $('#progress-text').text(Math.round(percentage * 100) + '%');
});

// 成功回调:上传完成,显示成功消息
uploadDeferred.done(function(response) {
  alert('上传成功!' + response.message);
});

// 失败回调:上传失败,显示错误信息
uploadDeferred.fail(function(error) {
  alert('上传失败:' + error.message);
});

// 模拟上传过程
function simulateUpload() {
  var progress = 0;
  var interval = setInterval(function() {
    progress += 0.1; // 每次增加 10%
    uploadDeferred.notify(progress);

    if (progress >= 1) {
      clearInterval(interval);
      uploadDeferred.resolve({ message: '文件已上传' });
    }
  }, 500);
}

// 启动上传
simulateUpload();

在这个例子中,我们同时使用了三种回调:

  • progress() 实时更新 UI。
  • done() 处理成功结果。
  • fail() 处理异常情况。

三者互不干扰,各自负责不同阶段,让代码结构更清晰,维护性更强。

实际应用场景:文件上传进度条

文件上传是 deferred.progress() 方法最典型的应用场景。浏览器原生的 XMLHttpRequest 支持 progress 事件,但封装成 jQuery 的 deferred 模式后,可以更统一地处理各种异步操作。

下面是一个完整的文件上传示例,包含进度条更新:

<div>
  <input type="file" id="file-upload" />
  <div class="progress">
    <div id="progress-bar" class="progress-bar" style="width: 0%;"></div>
  </div>
  <p id="progress-text">0%</p>
</div>
$('#file-upload').on('change', function(e) {
  var file = e.target.files[0];
  var formData = new FormData();
  formData.append('file', file);

  var uploadDeferred = $.Deferred();

  // 进度回调:更新进度条
  uploadDeferred.progress(function(percentage) {
    $('#progress-bar').css('width', (percentage * 100) + '%');
    $('#progress-text').text(Math.round(percentage * 100) + '%');
  });

  // 成功回调
  uploadDeferred.done(function(response) {
    alert('上传成功:' + response.filename);
  });

  // 失败回调
  uploadDeferred.fail(function(xhr, status, error) {
    alert('上传失败:' + error);
  });

  // 使用 jQuery.ajax 发起上传请求
  $.ajax({
    url: '/upload',
    type: 'POST',
    data: formData,
    contentType: false,
    processData: false,
    xhr: function() {
      var xhr = new window.XMLHttpRequest();
      // 监听上传进度
      xhr.upload.addEventListener('progress', function(e) {
        if (e.lengthComputable) {
          var percent = e.loaded / e.total;
          uploadDeferred.notify(percent); // 触发 progress 回调
        }
      }, false);
      return xhr;
    }
  })
  .done(function(response) {
    uploadDeferred.resolve(response);
  })
  .fail(function(xhr, status, error) {
    uploadDeferred.reject(xhr, status, error);
  });
});

这个例子中:

  • xhr.upload.addEventListener('progress') 是原生的进度监听。
  • uploadDeferred.notify(percent) 将原生事件转换为 jQuery 的 deferred 机制。
  • 所有 UI 更新都通过 progress() 回调完成,逻辑清晰。

高级用法:多个 progress 回调与链式调用

deferred.progress() 支持注册多个回调函数,它们会按顺序执行。这在需要多个组件响应进度时非常有用。

var task = $.Deferred();

// 注册多个进度回调
task.progress(function(perc) {
  console.log('模块 A:进度更新到 ' + (perc * 100).toFixed(1) + '%');
});

task.progress(function(perc) {
  console.log('模块 B:当前进度 ' + (perc * 100).toFixed(1) + '%');
});

// 模拟任务
setTimeout(function() {
  task.notify(0.25);
}, 1000);

setTimeout(function() {
  task.notify(0.75);
}, 2000);

setTimeout(function() {
  task.resolve();
}, 3000);

输出结果:

模块 A:进度更新到 25.0%
模块 B:当前进度 25.0%
模块 A:进度更新到 75.0%
模块 B:当前进度 75.0%

此外,deferred.progress() 可以与 then()always() 等方法链式使用,形成完整的异步流程:

var deferred = $.Deferred();

deferred.progress(function(perc) {
  console.log('当前进度:' + (perc * 100).toFixed(2) + '%');
});

deferred.then(
  function(success) {
    console.log('任务成功:', success);
  },
  function(error) {
    console.log('任务失败:', error);
  }
);

// 触发进度
deferred.notify(0.5);
deferred.resolve('任务完成');

总结

jQuery deferred.progress() 方法是 jQuery 异步编程中一个不可或缺的工具。它让开发者能够实时感知异步任务的执行状态,为用户界面提供流畅的反馈体验。

无论是文件上传、动画播放,还是复杂的数据处理,只要涉及到“进行中”的状态,progress() 都能派上用场。它与 done()fail() 配合使用,构建出完整、清晰的异步流程。

虽然现代 JavaScript 已经有了 Promiseasync/await,但 jQuery 的 deferred 机制在特定场景下依然有其价值,尤其是在维护老项目或需要兼容旧版浏览器时。

掌握 deferred.progress() 方法,不仅能让你写出更友好的前端应用,也能加深对异步编程本质的理解。它提醒我们:异步不只是“成功或失败”,还有“正在发生”的中间状态。而这个状态,正是用户体验的分水岭。