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

JavaScript 从零基础到精通系列:异步编程与网络请求

摘要:网页常常需要从服务器获取数据而无需刷新页面,这就需要异步操作。本篇将逐步讲解 JavaScript 的异步模型:回调函数、Promise 和 async/await。你将学会使用 Fetch API 与服务器交互,理解事件循环的基本概念,并在此基础上搭建一个实时汇率查询小应用,跨入前后端数据交互的大门。


一、同步与异步

JavaScript 是单线程语言,一个时间只能做一件事。如果任务耗时很长(比如网络请求),同步执行会阻塞页面,导致卡顿。因此,浏览器提供了异步 API(定时器、AJAX、事件监听等),这些操作交给浏览器其他线程处理,完成后通过回调函数通知 JS 主线程。

二、回调函数与回调地狱

最基础的异步模式就是回调函数。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>异步执行顺序</title> </head> <body> <script> console.log('========== 代码开始执行 =========='); // 1. 同步任务:立刻执行 console.log('① 开始'); // 2. 异步任务:放入任务队列,等待 1秒 后执行 setTimeout(() => { console.log('③ 1 秒后执行(异步任务)'); }, 1000); // 3. 同步任务:立刻执行 console.log('② 结束'); console.log('========== 同步代码执行完毕 =========='); </script> </body> </html>

当多个异步任务需要顺序执行时,会出现“回调地狱”,代码层层嵌套,难以维护:

// 模拟地狱 getUser(userId, function(user) { getPosts(user.id, function(posts) { getComments(posts[0].id, function(comments) { // ... }); }); });

三、Promise:优雅的异步方案

ES6 引入的 Promise 对象,代表一个异步操作的最终完成或失败。

基本创建和消费

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Promise 完整示例</title> </head> <body> <script> // 1. 创建 Promise 对象(封装异步任务) const promise = new Promise((resolve, reject) => { console.log("1. 开始执行异步操作..."); // 模拟异步请求(定时器) setTimeout(() => { const success = true; // 你可以改成 false 测试失败情况 if (success) { // ✅ 成功:调用 resolve,把结果传给 .then() resolve("✅ 数据获取成功"); } else { // ❌ 失败:调用 reject,把错误传给 .catch() reject("❌ 出错了:网络请求失败"); } }, 1500); }); // 2. 使用 Promise promise .then((result) => { console.log("2. then 收到:", result); return "✅ 下一步处理数据"; // 可以继续传递给下一个 then }) .then((nextResult) => { console.log("3. 第二个 then 收到:", nextResult); }) .catch((error) => { // ❌ 捕获所有错误 console.error("❌ 捕获异常:", error); }) .finally(() => { // 🎯 无论成功/失败 都会执行 console.log("🎯 finally:无论成败,我都会执行!"); }); </script> </body> </html>

Promise 的链式调用完美解决了回调地狱,错误可以被最尾端的catch捕获。

常用静态方法

  • Promise.resolve(value)/Promise.reject(reason)

  • Promise.all([p1, p2, ...]):所有 Promise 都成功才成功,返回结果数组,一个失败整体失败。

  • Promise.allSettled([p1, p2]):等所有 Promise 敲定,不管成功失败,返回状态数组(ES2020)。

  • Promise.race([p1, p2]):返回第一个敲定的 Promise 的结果。

四、async/await:让异步代码像同步

ES2017 引入的async/await是 Promise 的语法糖,使得异步代码写起来像同步,可读性大大提高。

async function fetchData() { try { // 发送网络请求 const response = await fetch('https://api.example.com/data'); // 如果网络响应失败(404/500),手动抛出错误 if (!response.ok) throw new Error('网络响应失败'); // 等待解析 JSON const data = await response.json(); // 打印并返回数据 console.log(data); return data; } catch (error) { // 捕获所有错误 console.error('请求失败:', error); } }

规则:

  • async函数自动返回一个 Promise。

  • await必须在async函数内使用,它会暂停函数执行,等待 Promise 完成。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>async/await 数据请求</title> </head> <body> <h3>请求结果:</h3> <pre id="result"></pre> <script> // 完善版 async/await 请求函数 async function fetchData() { const resultDom = document.getElementById('result'); try { // 显示加载中 resultDom.textContent = "加载中..."; // 1. 发送请求(使用真实公开接口) const response = await fetch('https://jsonplaceholder.typicode.com/todos/1'); // 2. 判断网络响应是否成功 if (!response.ok) { throw new Error(`请求错误:${response.status}`); } // 3. 解析 JSON 数据 const data = await response.json(); console.log('✅ 获取成功:', data); // 4. 显示到页面 resultDom.textContent = JSON.stringify(data, null, 2); return data; } catch (error) { // 统一捕获所有错误:网络错误、逻辑错误、解析错误 console.error('❌ 请求失败:', error.message); resultDom.textContent = '请求失败:' + error.message; return null; } } // 执行请求 fetchData(); </script> </body> </html>

五、Fetch API:现代网络请求

fetch()是浏览器内置的、基于 Promise 的 API,取代了老旧的 XMLHttpRequest。

GET 请求

fetch('https://api.github.com/users/octocat') .then(res => res.json()) .then(data => console.log(data)) .catch(err => console.error(err));

POST 请求

fetch('https://jsonplaceholder.typicode.com/posts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'foo', body: 'bar', userId: 1 }) }) .then(res => res.json()) .then(data => console.log('创建成功:', data));

处理响应response.ok判断状态码是否在 200-299。response.json()解析 JSON,此外还有.text().blob()等。

六、事件循环 (Event Loop) 宏观理解

了解事件循环对写出高效的异步代码很有帮助。简单模型:

调用栈(Call Stack)执行同步代码。

遇到异步 API(如 setTimeout、fetch),交给浏览器其他线程处理,处理完后回调放入任务队列(宏任务与微任务)。

当调用栈清空时,事件循环先清空微任务队列(Promise.then/catch、MutationObserver),再取出一个宏任务(setTimeout、setInterval、I/O)执行,循环往复。

console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4'); // 输出:1 4 3 2

七、实战:实时汇率转换器

我们将使用免费汇率 API (exchangerate-api.com 示例) 来构建一个货币转换器。为了安全,API key 应放在后端,这里示例仅作学习。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>汇率转换器</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: Arial, sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #f5f7fa; } .converter { background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); width: 400px; text-align: center; } input, select, button { width: 100%; padding: 10px; margin: 8px 0; border-radius: 6px; border: 1px solid #ddd; font-size: 16px; } button { background: #007bff; color: white; border: none; cursor: pointer; } button:hover { background: #0056b3; } #result { margin-top: 15px; font-size: 18px; font-weight: bold; color: #333; } .tip { font-size: 12px; color: #666; margin-top: 10px; } </style> </head> <body> <div class="converter"> <h2>汇率转换器</h2> <!-- 输入要转换的金额 --> <input type="number" id="amount" placeholder="请输入金额" value="1"> <!-- 原始货币下拉框 --> <select id="fromCurrency"></select> <span>→</span> <!-- 目标货币下拉框 --> <select id="toCurrency"></select> <!-- 转换按钮 --> <button id="convertBtn">立即转换</button> <!-- 结果显示区域 --> <p id="result"></p> <div class="tip">实时汇率来源:exchangerate-api.com</div> </div> <script> // 🔸 API 地址:获取美元为基准的所有货币汇率 const API_URL = 'https://open.er-api.com/v6/latest/USD'; // 🔸 用来存储所有货币的汇率对象(全局方便调用) let rates = {}; // ============================================== // 异步函数:从 API 获取最新汇率 // ============================================== async function fetchRates() { // 获取结果显示元素 const resultEl = document.getElementById('result'); // 页面提示:正在加载 resultEl.textContent = '加载汇率中...'; try { // 1. 发送网络请求获取汇率数据 const response = await fetch(API_URL); // 2. 判断请求是否成功(状态码 200-299) if (!response.ok) throw new Error('获取汇率失败'); // 3. 将返回的数据解析为 JSON 格式 const data = await response.json(); // 4. 将汇率数据存入全局变量 rates = data.rates; // 5. 把货币代码填充到下拉选择框 populateSelectors(Object.keys(rates)); // 6. 加载完成提示 resultEl.textContent = '加载完成,请开始转换'; } catch (err) { // 捕获错误:网络失败、接口异常等 resultEl.textContent = '⚠️ 加载汇率失败'; console.error('错误信息:', err); } } // ============================================== // 函数:将货币代码填充到两个下拉框 // currencies:货币代码数组,如 ['USD','CNY','EUR'] // ============================================== function populateSelectors(currencies) { // 获取两个下拉框元素 const fromSelect = document.getElementById('fromCurrency'); const toSelect = document.getElementById('toCurrency'); // 循环所有货币代码,添加到下拉选项 currencies.forEach(code => { // 创建选项:new Option(显示文字, value值) fromSelect.add(new Option(code, code)); toSelect.add(new Option(code, code)); }); // 🔸 设置默认选中值:美元 → 人民币 fromSelect.value = 'USD'; toSelect.value = 'CNY'; } // ============================================== // 点击转换按钮执行逻辑 // ============================================== document.getElementById('convertBtn').addEventListener('click', () => { // 1. 获取输入框金额 const amountInput = document.getElementById('amount'); const amount = parseFloat(amountInput.value); // 2. 获取选中的原始货币 和 目标货币 const from = document.getElementById('fromCurrency').value; const to = document.getElementById('toCurrency').value; // -------------------------- // 🔸 校验输入是否合法 // -------------------------- // 如果不是数字 或 金额 ≤ 0,提示错误 if (isNaN(amount) || amount <= 0) { alert('请输入有效的金额!'); amountInput.focus(); // 让输入框重新聚焦 return; // 停止执行 } // 如果汇率还没加载完成,不能转换 if (!rates[from] || !rates[to]) { alert('货币汇率未加载完成,请稍候'); return; } // -------------------------- // 🔸 核心汇率计算公式 // -------------------------- // 公式:目标金额 = 输入金额 / 原始货币汇率 * 目标货币汇率 const result = (amount / rates[from]) * rates[to]; // -------------------------- // 显示结果(保留 2 位小数) // -------------------------- document.getElementById('result').textContent = `${amount} ${from} = ${result.toFixed(2)} ${to}`; }); // ============================================== // 页面一加载就自动获取汇率 // ============================================== fetchRates(); </script> </body> </html>

这个项目完美融合了async/awaitfetch、DOM 操作和事件监听,体现了真实项目的开发流程。


总结: 我们从回调函数讲到 Promise 再到 async/await,这是现代 JavaScript 异步编程的主线。掌握了 Fetch API,你就打开了与服务器通信的大门。事件循环的知识帮助你写出更可靠、更高效的代码。此刻,你已具备了前后端交互的核心技能。接下来,我们将进行最后的拼图:面向对象、模块化,并运用全套知识打造一个大型项目——我的任务管家。


如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享,也可以留言告诉我你遇到的其它问题,我会尽快回复。动手练习是掌握编程最快的方法,请务必亲手敲一遍本文的所有示例代码,并截图保存你的成果。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。

http://www.rkmt.cn/news/1435914.html

相关文章:

  • Illustrator画板调整终极指南:一键同步缩放画板与对象
  • 高效实战指南:快速掌握BiRefNet图像分割的核心技巧
  • 找质数,不止暴力试除——埃拉托色尼筛法与线性筛
  • 蓝奏云直链解析API:基于PHP的云端文件访问自动化解决方案
  • 传统运动必须固定场地,编写全场景移动运动适配程序,任何场景都适配运动,打破场地限制,
  • Video2X终极指南:如何用AI让老旧视频秒变4K高清大片
  • 为什么你的Gemini账单翻倍了?——资深MLOps工程师逐行比对新旧计费规则(含12个隐藏费用触发点)
  • Zotero Style插件终极指南:如何解决高能进度条显示问题
  • Python算法基础篇之背包问题
  • 传统规划必须长期宏大,编写短期微规划生成程序,主打小周期落地,颠覆远大空长期规划。
  • 跨平台资源下载终极指南:3分钟掌握res-downloader的完整使用技巧
  • 2026杭州GEO优化服务商如何选?深度避坑与爱搜索GEO解析 - 品牌报告
  • DLSS Swapper深度解析:告别手动替换,智能管理游戏DLSS文件的技术革命
  • 供应链管理入门到底怎么样? - 众智商学院职业教育
  • AI 应用安全最佳实践:保护数据和系统安全
  • 普通数转换为二进制数的方法
  • 终极解决方案:D2DX让暗黑破坏神2在现代PC上焕发新生
  • 多模态记忆:让 AI Agent 记忆各种类型的信息
  • 2026年4月行业内比较好的轨距拉杆直销厂家找哪家,道钉锚固剂/鱼尾螺栓/RGV轨道/轨距拉杆,轨距拉杆公司哪个好 - 品牌推荐师
  • AI儿童绘本生成:技术架构、实战难点与未来展望
  • 2026 年贵州铜仁职业培训怎么选?本地综合培训机构全面解析 - 资讯纵览
  • 【Gemini诗歌生成高阶秘籍】:20年AI内容专家亲授7大避坑法则与韵律控制心法
  • 为什么92%的Gemini私有部署未启用内存隔离?——2024 Q2第三方审计报告首次公开,含3步热修复补丁
  • Windows微信QQ防撤回终极指南:一键永久保存所有消息的完整教程
  • Xenia Canary终极指南:5个专业技巧实现Xbox 360游戏完美模拟
  • 基于Arduino Leonardo的街机外设DIY:从HID原理到实战开发
  • GPT还是MBR?给SATA/NVMe固态硬盘分区选错,重装系统白忙活
  • 基于Arduino Leonardo的头部控制游戏控制器设计与实现
  • 避坑指南:用Python做DEA效率分析时,为什么你的SBM模型结果总不对?
  • 基于Arduino的智能宠物模拟装置:温度触发与振动反馈的硬件实现