You-Dont-Need-jQuery API完全参考:Promise与异步编程篇

You-Dont-Need-jQuery API完全参考:Promise与异步编程篇

【免费下载链接】You-Dont-Need-jQuery Examples of how to do query, style, dom, ajax, event etc like jQuery with plain javascript. 【免费下载链接】You-Dont-Need-jQuery 项目地址: https://gitcode.com/gh_mirrors/yo/You-Dont-Need-jQuery

你是否还在为jQuery的异步操作API与原生JavaScript的Promise语法混淆而烦恼?是否想摆脱第三方库依赖,用更现代的方式处理异步逻辑?本文将系统对比jQuery与原生Promise的核心API,通过实用示例帮你彻底掌握异步编程范式转换。读完本文,你将能够:用原生Promise重构jQuery Deferred代码、掌握Fetch API替代$.ajax的全场景方案、解决异步操作中的错误处理难题。

异步编程范式演进

从回调地狱到Promise链

传统jQuery异步代码依赖嵌套回调,容易导致"回调地狱":

// jQuery回调嵌套写法
$.getJSON('/api/user', function(user) {
  $.getJSON(`/api/posts/${user.id}`, function(posts) {
    $.getJSON(`/api/comments/${posts[0].id}`, function(comments) {
      // 多层嵌套导致代码可读性差
      renderComments(comments);
    }).fail(handleError);
  }).fail(handleError);
}).fail(handleError);

原生Promise通过链式调用解决嵌套问题,代码结构更扁平:

// 原生Promise链式写法
fetch('/api/user')
  .then(response => response.json())
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(response => response.json())
  .then(posts => fetch(`/api/comments/${posts[0].id}`))
  .then(response => response.json())
  .then(comments => renderComments(comments))
  .catch(handleError); // 单一错误处理点

Promise核心概念

Promise(承诺) 是异步操作的容器,代表未来某个时间点会完成的操作及其结果。一个Promise有三种状态:

  • Pending(进行中):初始状态,既未成功也未失败
  • Fulfilled(已成功):操作完成并返回结果
  • Rejected(已失败):操作出错并返回原因

状态转换是单向的,一旦从Pending变为Fulfilled或Rejected,就会凝固不变。

jQuery Promise vs 原生Promise API对比

基础用法对比

功能jQuery实现原生实现
创建Promise$.Deferred()new Promise()
成功回调.done().then()
失败回调.fail().catch()
无论成败都执行.always().finally()
多个Promise并行$.when()Promise.all()

done/fail/always 替代方案

jQuery的.done().fail().always()方法可以用原生Promise的.then().catch()完全替代:

// jQuery写法
$.ajax('/api/data')
  .done(data => console.log('成功:', data))
  .fail(error => console.error('失败:', error))
  .always(() => console.log('无论成败都执行'));

// 原生写法
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log('成功:', data))
  .catch(error => console.error('失败:', error))
  .finally(() => console.log('无论成败都执行'));

注意:原生.finally()是ES2018新增特性,IE不支持,如需兼容可使用polyfill

多Promise并行处理

jQuery的$.when()用于等待多个异步操作完成,对应原生的Promise.all()

// jQuery写法
$.when($.get('/api/user'), $.get('/api/settings'))
  .done((userResp, settingsResp) => {
    const user = userResp[0];
    const settings = settingsResp[0];
    initApp(user, settings);
  });

// 原生写法
Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/settings').then(r => r.json())
])
.then(([user, settings]) => {
  initApp(user, settings);
})
.catch(error => {
  console.error('任一请求失败:', error);
});

关键区别Promise.all()在任意一个Promise reject时会立即 reject,而$.when()会等待所有操作完成(包括失败的)。如果需要等待所有Promise完成(无论成功失败),可使用Promise.allSettled()

Deferred对象与Promise构造器

创建异步操作

jQuery使用$.Deferred()创建可控制的异步操作,而原生JavaScript使用new Promise()构造器:

// jQuery Deferred写法
function getAsyncData() {
  const defer = $.Deferred();
  setTimeout(() => {
    if (Math.random() > 0.5) {
      defer.resolve({ success: true, data: '模拟数据' });
    } else {
      defer.reject(new Error('随机失败'));
    }
  }, 1000);
  return defer.promise();
}

// 原生Promise写法
function getAsyncData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve({ success: true, data: '模拟数据' });
      } else {
        reject(new Error('随机失败'));
      }
    }, 1000);
  });
}

手动控制Promise状态

原生Promise的状态一旦改变就无法修改,而jQuery Deferred允许外部控制状态。如需原生实现类似功能,可创建Deferred封装:

// 原生模拟Deferred模式
function createDeferred() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

// 使用示例
const { promise, resolve, reject } = createDeferred();
promise.then(data => console.log('成功:', data));

// 外部控制状态
setTimeout(() => {
  resolve('延迟2秒后手动解决');
}, 2000);

Fetch API:替代$.ajax的现代方案

Fetch API是原生JavaScript用于替代XMLHttpRequest的新标准,返回Promise对象,完美契合异步编程需求。

基本GET请求

// jQuery ajax
$.get('/api/data', { id: 123 })
  .done(data => console.log(data))
  .fail(err => console.error(err));

// 原生Fetch
fetch('/api/data?id=123')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP错误: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(err => console.error(err));

注意:Fetch不会对HTTP错误状态码(如404、500) reject,需要手动检查response.ok属性。

POST请求与请求配置

// jQuery ajax POST
$.ajax({
  url: '/api/users',
  method: 'POST',
  data: JSON.stringify({ name: 'John', age: 30 }),
  contentType: 'application/json',
  success: data => console.log('创建成功:', data),
  error: err => console.error('创建失败:', err)
});

// 原生Fetch POST
fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ name: 'John', age: 30 }),
})
.then(response => {
  if (!response.ok) throw new Error(response.statusText);
  return response.json();
})
.then(data => console.log('创建成功:', data))
.catch(err => console.error('创建失败:', err));

带超时处理的Fetch

原生Fetch不内置超时功能,可通过Promise.race()实现:

// 带超时的Fetch请求
function fetchWithTimeout(url, options = {}, timeout = 5000) {
  // 创建超时Promise
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`请求超时(${timeout}ms)`));
    }, timeout);
  });

  // 竞赛:谁先完成就用谁的结果
  return Promise.race([
    fetch(url, options),
    timeoutPromise
  ]);
}

// 使用示例
fetchWithTimeout('/api/slow-endpoint', {}, 3000)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error('请求失败:', err.message));

高级异步模式

Async/Await:Promise的语法糖

ES2017引入的async/await语法让异步代码看起来像同步代码,本质是Promise的语法糖:

// 使用async/await重写Fetch请求
async function loadUserData(userId) {
  try {
    // 等待Promise解决
    const userResponse = await fetch(`/api/users/${userId}`);
    if (!userResponse.ok) throw new Error('用户数据请求失败');
    
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/posts?user=${userId}`);
    if (!postsResponse.ok) throw new Error('文章数据请求失败');
    
    user.posts = await postsResponse.json();
    return user;
  } catch (error) {
    console.error('加载失败:', error);
    throw error; // 允许上层处理错误
  }
}

// 调用async函数
loadUserData(123)
  .then(user => renderUserProfile(user))
  .catch(error => showErrorUI(error));

Promise并发控制

Promise.all():全部完成

Promise.all()接收Promise数组,等待所有Promise完成后返回结果数组:

// 并行加载多个资源
async function loadDashboardData() {
  try {
    const [user, projects, notifications] = await Promise.all([
      fetch('/api/user').then(r => r.json()),
      fetch('/api/projects').then(r => r.json()),
      fetch('/api/notifications').then(r => r.json())
    ]);
    
    return { user, projects, notifications };
  } catch (error) {
    console.error('加载面板数据失败:', error);
    // 单个请求失败导致整个Promise.all失败
  }
}
Promise.allSettled():全部完成(无论成败)

ES2020新增的Promise.allSettled()等待所有Promise完成,无论成功或失败:

// 并行执行多个独立请求,获取所有结果
async function batchOperation() {
  const results = await Promise.allSettled([
    fetch('/api/task1').then(r => r.json()),
    fetch('/api/task2').then(r => r.json()),
    fetch('/api/task3').then(r => r.json())
  ]);
  
  // 筛选成功和失败的结果
  const successes = results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
    
  const failures = results
    .filter(r => r.status === 'rejected')
    .map(r => r.reason);
    
  return { successes, failures };
}
Promise.race():谁先完成用谁的

Promise.race()接收Promise数组,返回第一个完成的Promise结果(无论成功失败):

// 实现请求重试机制
function fetchWithRetry(url, retries = 3, delay = 1000) {
  return fetch(url)
    .then(response => {
      if (!response.ok) throw new Error(response.statusText);
      return response;
    })
    .catch(error => {
      if (retries > 0) {
        // 延迟后重试
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(fetchWithRetry(url, retries - 1, delay));
          }, delay);
        });
      }
      throw error; // 重试次数用尽
    });
}

错误处理最佳实践

全局错误捕获

在实际应用中,建议在全局层面捕获未处理的Promise错误:

// 浏览器环境全局未捕获Promise错误
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的Promise错误:', event.reason);
  // 可以在这里显示全局错误提示
  event.preventDefault(); // 阻止默认处理(如控制台警告)
});

// Node.js环境
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的Promise错误:', reason);
});

错误处理策略

  1. 精确捕获:针对特定错误类型进行处理
  2. 错误转换:将低级错误转换为业务错误
  3. 错误恢复:在某些情况下可以尝试恢复操作
async function safeDataLoad() {
  try {
    return await fetch('/api/critical-data');
  } catch (error) {
    // 错误类型判断
    if (error.name === 'AbortError') {
      console.log('请求已取消');
      return null;
    } else if (error.message.includes('404')) {
      console.log('资源不存在,使用默认数据');
      return { default: true, data: [] }; // 错误恢复
    } else if (navigator.onLine) {
      // 有网络但其他错误,尝试重试一次
      console.log('重试一次...');
      return fetch('/api/critical-data');
    } else {
      // 网络错误,提示用户
      showUserMessage('网络连接已断开,请检查网络');
      throw new Error('网络错误', { cause: error }); // 错误包装
    }
  }
}

浏览器兼容性与Polyfill

兼容性概览

特性ChromeFirefoxEdgeSafariIE
Promise32+29+12+7.1+
Fetch API42+39+14+10.1+
async/await55+52+15+11+
Promise.allSettled76+71+79+13.1+

引入Polyfill

对于需要支持旧浏览器的项目,可以引入必要的polyfill:

<!-- 引入Promise polyfill (IE11及以下需要) -->
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>

<!-- 引入Fetch polyfill -->
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js"></script>

从jQuery迁移到原生Promise的步骤

  1. 识别异步代码:找出项目中使用$.ajax$.Deferred$.when的代码
  2. 替换基础API
    • $.ajaxfetch
    • $.Deferrednew Promise
    • $.whenPromise.all
  3. 重构回调链:将.done()/.fail()链转换为.then()/.catch()
  4. 错误处理统一:确保所有异步操作有适当的错误处理
  5. 逐步迁移:先在新功能中使用原生API,再逐步替换旧代码
  6. 添加polyfill:针对目标浏览器添加必要的polyfill
  7. 测试验证:特别注意边缘情况和错误处理逻辑

总结与展望

从jQuery的Deferred到原生Promise,异步编程范式的转变不仅是API的替换,更是代码组织方式的升级。原生Promise带来了:

  • 语言级标准化:无需依赖第三方库
  • 更好的错误处理:统一的catch机制
  • 与现代JS特性融合:async/await大幅提升可读性
  • 更丰富的异步模式:race、allSettled等高级功能

随着Web平台的持续发展,Web Streams APIAbortController等新特性进一步增强了异步编程能力。掌握原生Promise不仅能减少代码体积,更能让你跟上JavaScript的发展潮流。

建议收藏本文作为API速查手册,关注项目README.zh-CN.md获取最新更新。你有哪些Promise使用心得或疑问?欢迎在评论区留言讨论!

下一篇预告:《You-Dont-Need-jQuery DOM操作完全指南》,将深入探讨如何用原生API替代jQuery的DOM操作方法。

【免费下载链接】You-Dont-Need-jQuery Examples of how to do query, style, dom, ajax, event etc like jQuery with plain javascript. 【免费下载链接】You-Dont-Need-jQuery 项目地址: https://gitcode.com/gh_mirrors/yo/You-Dont-Need-jQuery

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值