当前位置: 首页 > ai >正文

【JavaScript】async/await 与 Fetch 传参,PUT,PATCH,文件上传,批量删除等前端案例

async/await 与 Fetch 传参:实战详解(后端视角)

理解 async/await 和 Fetch 的工作方式,能快速定位前后端接口交互问题比如参数没收到、请求方式错误等)。

一、async/await 具体用法:用同步的方式写异步代码

async/await 是 Promise 的“语法糖”,目的是让异步代码(比如调用后端接口)写起来像同步代码一样直观。

核心规则:

  1. await 只能用在 async 修饰的函数里
  2. await 后面必须跟一个 Promise 对象(比如 Fetch 的返回值)
  3. 遇到 await 时,函数会“暂停”,等待 promise 完成后再继续执行

实战示例:调用后端登录接口

假设后端有一个登录接口:

  • 地址:/api/login
  • 方法:POST
  • 参数:{ username: string, password: string }
  • 返回:{ code: 200, data: { token: string }, msg: "success" }

前端调用代码:

// 1. 用 async 修饰函数,使其成为异步函数
async function login(username, password) {try {// 2. 用 await 等待 Fetch 请求完成(Fetch 返回 Promise)const response = await fetch('/api/login', {method: 'POST',headers: {'Content-Type': 'application/json' // 告诉后端参数是 JSON 格式},body: JSON.stringify({ username, password }) // 转换为 JSON 字符串});// 3. 等待响应体解析为 JSON(response.json() 也返回 Promise)const result = await response.json();// 4. 处理后端返回结果(同步写法,逻辑清晰)if (result.code === 200) {console.log('登录成功,token:', result.data.token);return result.data.token; // 返回 token 给调用者} else {console.log('登录失败:', result.msg);throw new Error(result.msg); // 抛出自定义错误}} catch (error) {// 5. 捕获所有错误(网络错误、后端报错、自己抛的错)console.log('登录过程出错:', error.message);return null;}
}// 调用异步函数(两种方式)
// 方式1:用 await(需要在另一个 async 函数里)
async function main() {const token = await login('admin', '123456');if (token) {// 登录成功后的逻辑(如跳转页面)}
}
main();// 方式2:用 .then()(兼容非 async 环境)
login('admin', '123456').then(token => {if (token) {// 登录成功后的逻辑}
});

后端视角的关键注解:

  • try/catch 能捕获所有异常:包括网络错误(如接口不通)、后端返回的错误状态(如 code=500)、甚至前端自己的代码错误(如变量未定义)。这对应后端的错误处理逻辑,但前端的 catch 更“万能”。
  • await 会“暂停”但不阻塞:函数内部会等,但整个 JS 线程不会卡(类似 Go 中 goroutine 等待 I/O 时会让出 CPU)。
  • 为什么要 await response.json()?因为 Fetch 分两步:先拿到响应头(response 对象),再异步解析响应体(response.json()),这和后端读取 HTTP 响应体的逻辑一致。

二、Fetch 如何传参:对应后端的 HTTP 请求解析

Fetch 是前端发起 HTTP 请求的主流 API,传参方式直接对应后端的参数接收逻辑(如 Go 的 r.FormValuer.Body 等)。不同请求方法(GET/POST/PUT/DELETE)的传参方式不同,这是前后端对接的高频问题点。

1. GET 请求:参数在 URL 上(对应后端的 Query 参数)

场景:获取用户列表(分页查询)

async function getUserList(page = 1, size = 10) {// 拼接 URL 参数(用 ? 分隔,& 连接多个参数)const url = `/api/users?page=${page}&size=${size}`;const response = await fetch(url, {method: 'GET' // 可以省略,Fetch 默认是 GET 方法// GET 方法没有 body,参数全在 URL 上});return await response.json();
}// 调用:获取第 2 页,每页 20 条
getUserList(2, 20);

后端接收:Go 中用 r.URL.Query().Get("page") 即可获取,和处理普通 HTTP GET 请求完全一致。

2. POST 请求:参数在请求体(Body)中

情况 A:JSON 格式(推荐,前后端数据结构对齐)

场景:创建新用户(参数较多时用 JSON)

async function createUser(userData) {const response = await fetch('/api/users', {method: 'POST',headers: {'Content-Type': 'application/json' // 必须加,告诉后端是 JSON},body: JSON.stringify(userData) // 转换为 JSON 字符串});return await response.json();
}// 调用:传递用户信息对象
createUser({name: '张三',age: 25,email: 'zhangsan@example.com'
});

后端接收:Go 中需要先解析 Body,如 json.NewDecoder(r.Body).Decode(&user),和处理 JSON 格式的 POST 请求一致。

情况 B:表单格式(application/x-www-form-urlencoded

场景:简单表单提交(如登录、搜索)

async function searchProducts(keyword) {// 用 URLSearchParams 处理表单参数const formData = new URLSearchParams();formData.append('keyword', keyword);formData.append('sort', 'price'); // 可以添加多个参数const response = await fetch('/api/products/search', {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded' // 表单格式},body: formData // 直接传 URLSearchParams 对象});return await response.json();
}// 调用:搜索关键词“手机”
searchProducts('手机');

后端接收:Go 中用 r.PostForm.Get("keyword") 即可,和处理表单提交的逻辑一致。

3. 带请求头(Headers)的请求:如认证、版本控制

场景:调用需要 Token 认证的接口

async function getOrderDetail(orderId) {// 从本地存储获取 Token(登录时后端返回的)const token = localStorage.getItem('token');const response = await fetch(`/api/orders/${orderId}`, {method: 'GET',headers: {'Authorization': `Bearer ${token}`, // 认证信息(JWT 常用格式)'X-API-Version': 'v2' // 自定义头(如接口版本)}});return await response.json();
}

后端接收:Go 中用 r.Header.Get("Authorization") 获取,用于身份验证逻辑。

4. Fetch 的“坑”:后端需要注意的细节

问题场景前端表现后端排查方向
Fetch 默认不携带 Cookie后端认为“未登录”,但前端确实登录过前端是否加了 credentials: 'include'(允许跨域携带 Cookie)
4xx/5xx 不触发 catch接口返回 400/500,但前端没进 catch前端需手动判断 response.okresponse.ok 只有 200-299 才为 true)
跨域请求失败控制台报 CORS 错误后端是否配置了跨域响应头(如 Access-Control-Allow-Origin

总结:后端需要掌握的核心点

  1. 看懂调用流程async function 里用 await fetch(...) 发起请求,try/catch 处理成功/失败,这是前端调用后端接口的标准模式。
  2. 参数对应关系
    • GET 请求参数在 URL 上 → 后端读 Query
    • POST 请求参数在 Body 里,JSON 格式 → 后端解析 JSON Body
    • 表单格式 → 后端读 PostForm
  3. 错误排查思路:如果前端说“接口没反应”,先看 Fetch 的 methodurl 是否正确;如果“参数没收到”,检查 Content-Type 与参数格式是否匹配(JSON 对应 application/json,表单对应 x-www-form-urlencoded)。

更多案例

企业级前端场景:文件上传、PUT请求及更多实战示例

在企业级应用中,前端与后端的交互会更加复杂,涉及文件处理、部分更新、批量操作等场景。以下是几个典型场景的具体实现,包含完整代码和前后端交互要点。

一、文件上传(Multipart/Form-Data)

企业级应用中常见的头像上传、报表导入、附件上传等场景,都需要使用multipart/form-data格式。

前端实现(文件上传)

// 企业级文件上传实现(带进度条和基本验证)
async function uploadFile(file, folderId) {// 1. 文件验证(企业级应用必备)const maxSize = 10 * 1024 * 1024; // 10MBif (file.size > maxSize) {throw new Error(`文件大小不能超过${maxSize/1024/1024}MB`);}const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];if (!allowedTypes.includes(file.type)) {throw new Error('只允许上传JPG、PNG和PDF文件');}// 2. 构建FormData(专门用于文件上传的格式)const formData = new FormData();formData.append('file', file); // 文件本身formData.append('folderId', folderId); // 额外参数:文件存放的文件夹IDformData.append('fileName', file.name); // 可选:自定义文件名// 3. 发起上传请求const response = await fetch('/api/files/upload', {method: 'POST',headers: {'Authorization': `Bearer ${localStorage.getItem('token')}` // 身份验证// 注意:上传文件时不要设置Content-Type,浏览器会自动处理},body: formData,// 4. 监控上传进度(企业级体验必备)onUploadProgress: (progressEvent) => {const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);console.log(`上传进度:${percent}%`);// 实际项目中会更新进度条UI}});const result = await response.json();if (!response.ok) {throw new Error(result.msg || '文件上传失败');}return result.data; // 返回后端生成的文件ID、访问URL等信息
}// 页面中使用示例
document.getElementById('fileInput').addEventListener('change', async (e) => {const file = e.target.files[0];if (!file) return;try {// 上传到ID为123的文件夹const fileInfo = await uploadFile(file, '123');console.log('上传成功', fileInfo);// 显示上传成功的文件链接或预览} catch (error) {console.error('上传失败', error.message);// 显示错误提示给用户}
});

后端视角注解

  • 前端使用FormData对象处理文件,对应Go后端需要用multipart包解析
  • 上传进度通过onUploadProgress监听,后端无需特殊处理
  • 企业级应用必须包含文件类型、大小验证,减轻后端压力
  • 实际项目中可能还会实现:
    • 断点续传(通过Range请求头)
    • 大文件分片上传(切割文件为多个部分依次上传)
    • 上传前MD5校验(避免重复上传)

二、PUT请求(资源全量更新)

PUT请求用于全量更新资源,在企业级应用中常用于完整更新一条记录(如更新用户完整信息、修改商品所有属性)。

前端实现(PUT请求)

// 全量更新用户信息(PUT请求典型场景)
async function updateUser(userId, userData) {// 1. 参数验证(企业级应用必备)if (!userId) {throw new Error('用户ID不能为空');}// 2. 发起PUT请求const response = await fetch(`/api/users/${userId}`, {method: 'PUT', // 使用PUT方法表示全量更新headers: {'Content-Type': 'application/json','Authorization': `Bearer ${localStorage.getItem('token')}`},body: JSON.stringify(userData) // 完整的用户信息对象});const result = await response.json();if (!response.ok) {throw new Error(result.msg || `更新用户失败(${response.status}`);}return result.data;
}// 使用示例:更新ID为1001的用户信息
const updatedData = {name: '张三',email: 'new-zhangsan@example.com',phone: '13800138000',department: '技术部',status: 1 // 1表示启用,0表示禁用
};try {const result = await updateUser('1001', updatedData);console.log('用户更新成功', result);
} catch (error) {console.error('更新失败', error.message);
}

后端视角注解

  • PUT请求语义上表示"全量更新",后端通常会要求提供完整的资源数据
  • 与POST的区别:PUT是幂等的(多次调用结果相同),适合更新操作
  • 企业级应用中,PUT请求通常需要:
    • 资源ID在URL中(如/api/users/{userId}
    • 请求体包含完整的资源数据
    • 后端会根据ID找到对应资源并完全替换其内容

三、PATCH请求(资源部分更新)

PATCH请求用于部分更新资源,在企业级应用中常用于只更新需要修改的字段(如只修改用户手机号、只更新订单状态)。

前端实现(PATCH请求)

// 部分更新订单状态(PATCH请求典型场景)
async function updateOrderStatus(orderId, newStatus, remark) {// 1. 构建只包含需要更新的字段的对象const updateData = {status: newStatus,remark: remark // 只更新这两个字段};// 2. 发起PATCH请求const response = await fetch(`/api/orders/${orderId}`, {method: 'PATCH', // 使用PATCH方法表示部分更新headers: {'Content-Type': 'application/json','Authorization': `Bearer ${localStorage.getItem('token')}`},body: JSON.stringify(updateData) // 只包含需要更新的字段});const result = await response.json();if (!response.ok) {throw new Error(result.msg || `更新订单状态失败(${response.status}`);}return result.data;
}// 使用示例:将订单ID为"ORD20230510001"的状态改为"已发货"
try {const result = await updateOrderStatus('ORD20230510001', 'shipped', '顺丰快递:SF1234567890');console.log('订单状态更新成功', result);
} catch (error) {console.error('更新失败', error.message);
}

后端视角注解

  • PATCH请求语义上表示"部分更新",后端只更新提供的字段
  • 与PUT的区别:PATCH不需要提供完整资源数据,只需要提供要修改的字段
  • 企业级应用中,PATCH常用于:
    • 状态更新(订单状态、审批状态等)
    • 部分字段修改(不影响其他字段)
    • 减少数据传输量(尤其资源字段较多时)

四、批量操作(批量删除、批量更新)

企业级应用中经常需要批量处理数据,如批量删除选中项、批量更新状态等。

前端实现(批量操作)

// 1. 批量删除选中的用户
async function batchDeleteUsers(userIds) {if (!userIds || userIds.length === 0) {throw new Error('请选择要删除的用户');}const response = await fetch('/api/users/batch-delete', {method: 'DELETE',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${localStorage.getItem('token')}`},body: JSON.stringify({ ids: userIds }) // 传递ID数组});const result = await response.json();if (!response.ok) {throw new Error(result.msg || '批量删除失败');}return result.data;
}// 2. 批量更新商品状态
async function batchUpdateProductsStatus(productIds, newStatus) {if (!productIds || productIds.length === 0) {throw new Error('请选择要更新的商品');}const response = await fetch('/api/products/batch-update', {method: 'PATCH',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${localStorage.getItem('token')}`},body: JSON.stringify({ids: productIds,status: newStatus,updatedBy: localStorage.getItem('userId') // 记录操作人})});const result = await response.json();if (!response.ok) {throw new Error(result.msg || '批量更新失败');}return result.data;
}// 使用示例
// 批量删除ID为1001、1002、1003的用户
try {const deleteResult = await batchDeleteUsers(['1001', '1002', '1003']);console.log(`成功删除${deleteResult.deletedCount}个用户`);
} catch (error) {console.error('删除失败', error.message);
}// 批量将商品设置为"下架"状态
try {const updateResult = await batchUpdateProductsStatus(['P2023001', 'P2023002'], 'inactive');console.log(`成功更新${updateResult.updatedCount}个商品`);
} catch (error) {console.error('更新失败', error.message);
}

后端视角注解

  • 批量操作通常使用专门的接口(如/batch-delete),而不是多次调用单个接口
  • 传递批量ID时,常用{ ids: [] }格式,后端可以一次性处理
  • 企业级应用中,批量操作需要注意:
    • 权限控制(是否允许批量操作)
    • 操作日志记录(记录谁批量操作了哪些资源)
    • 事务处理(确保批量操作要么全部成功,要么全部失败)
    • 性能考虑(大批量操作可能需要异步处理)

五、企业级请求封装(Axios为例)

在实际企业项目中,不会直接使用原生Fetch,而是封装一层请求工具(如Axios),统一处理认证、错误、拦截等。

前端实现(请求封装)

// 企业级请求工具封装(基于Axios)
import axios from 'axios';// 创建axios实例
const request = axios.create({baseURL: import.meta.env.VITE_API_BASE_URL, // 从环境变量获取基础URLtimeout: 30000, // 超时时间30秒headers: {'Content-Type': 'application/json'}
});// 请求拦截器:添加认证信息、处理请求前逻辑
request.interceptors.request.use((config) => {// 1. 添加Token认证const token = localStorage.getItem('token');if (token) {config.headers.Authorization = `Bearer ${token}`;}// 2. 处理特殊格式(如文件上传)if (config.isFormData) {config.headers['Content-Type'] = 'multipart/form-data';}// 3. 记录请求日志(生产环境可关闭)console.log(`[请求] ${config.method} ${config.url}`, config.data);return config;},(error) => {// 请求发送失败(如网络错误)return Promise.reject(error);}
);// 响应拦截器:统一处理响应、错误
request.interceptors.response.use((response) => {const { data, config } = response;// 1. 记录响应日志console.log(`[响应] ${config.method} ${config.url}`, data);// 2. 统一处理业务错误(如Token过期、无权限)if (data.code !== 200) {// Token过期,跳转登录页if (data.code === 401) {localStorage.removeItem('token');window.location.href = '/login';return Promise.reject(new Error('登录已过期,请重新登录'));}// 其他业务错误return Promise.reject(new Error(data.msg || '操作失败'));}// 3. 只返回数据部分,简化使用return data.data;},(error) => {// 处理HTTP错误(如404、500)let message = '网络异常,请稍后重试';if (error.response) {const { status, statusText } = error.response;message = `请求失败(${status}):${statusText}`;// 记录HTTP错误日志console.error(`[HTTP错误] ${status}`, error.response.config.url);}return Promise.reject(new Error(message));}
);// 封装常用请求方法,简化使用
export default {get(url, params) {return request.get(url, { params });},post(url, data, config = {}) {return request.post(url, data, config);},put(url, data) {return request.put(url, data);},patch(url, data) {return request.patch(url, data);},delete(url, data) {return request.delete(url, { data });},// 专门的文件上传方法upload(url, formData, onProgress) {return request.post(url, formData, {timeout: 60000, // 上传超时时间延长到60秒onUploadProgress: onProgress,isFormData: true});}
};

后端视角注解

  • 企业级封装会统一处理:
    • 基础URL(方便切换开发/测试/生产环境)
    • 认证信息(Token自动添加)
    • 错误处理(包括HTTP错误和业务错误)
    • 超时设置(不同操作设置不同超时)
  • 后端接口设计需要配合这种封装:
    • 返回统一格式(如{ code, data, msg }
    • 使用标准HTTP状态码(200成功,401未授权等)
    • 提供清晰的错误信息(方便前端展示给用户)

这些企业级场景覆盖了大部分前后端交互需求,理解这些实现方式能帮助你更好地与前端团队协作,更快地定位和解决接口问题。如果需要了解某个具体场景的更多细节,可以继续提问。

http://www.xdnf.cn/news/19402.html

相关文章:

  • 二、Git基础命令速查表
  • Goframe 框架下HTTP反向代理并支持MCP所需的SSE协议的实现
  • leetcode算法刷题的第二十三天
  • Windows Qt5.15.17源码使用VS2019编译安装
  • Linux自动化构建工具-make/Makefile
  • C#/.NET/.NET Core技术前沿周刊 | 第 52 期(2025年8.25-8.31)
  • 【论文精读】基于YOLOv3算法的高速公路火灾检测
  • Jenkins 自动构建Vue 项目的一个大坑
  • 计算机毕设选题:基于Python+Django的健康饮食管理系统设计【源码+文档+调试】
  • 【LeetCode 155】—最小栈 - 详解与实现
  • Apache Commons ConvertUtils
  • 电科金仓 KFS 场景化实践路径解析:从行业场景落地看技术价值转化
  • Redis面试重点-2
  • std::thread详解
  • JDK14安装步骤及下载(附小白详细教程)
  • 在Unity中,让子物体不随父物体移动或转动的方法!
  • 数据库索引abc,请问查询哪些字段能命中索引
  • APB验证VIP Agent的各个组件之间的通信
  • 【C++ 】string类:深拷贝与浅拷贝解析
  • ​​告别通用模型局限:5步微调实战指南​
  • 数值分析——非线性方程与方程组的数值解法之迭代法
  • [灵动微电子 MM32BIN560CN MM32SPIN0280]读懂电机MCU 模拟输入运放放大
  • NCCL-TEST ib集群测试UCX代替方案
  • unity tilemap grid 的中心轴
  • Linux中卸载和安装Nginx
  • Python爬虫实战:研究Figures与 Axes,构建社交平台具有决策价值的数据采集和分析系统
  • C 语言进程通信之信号API
  • python---封装
  • MySQL 8 的 SQL 语法新特性
  • 《哲思:生命与宇宙的终极意义》