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

JavaScript 从零基础到精通系列:异步编程与网络请求 摘要网页常常需要从服务器获取数据而无需刷新页面这就需要异步操作。本篇将逐步讲解 JavaScript 的异步模型回调函数、Promise 和 async/await。你将学会使用 Fetch API 与服务器交互理解事件循环的基本概念并在此基础上搭建一个实时汇率查询小应用跨入前后端数据交互的大门。一、同步与异步JavaScript 是单线程语言一个时间只能做一件事。如果任务耗时很长比如网络请求同步执行会阻塞页面导致卡顿。因此浏览器提供了异步 API定时器、AJAX、事件监听等这些操作交给浏览器其他线程处理完成后通过回调函数通知 JS 主线程。二、回调函数与回调地狱最基础的异步模式就是回调函数。!DOCTYPE html html langzh-CN head meta charsetUTF-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 langzh-CN head meta charsetUTF-8 titlePromise 完整示例/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 /htmlPromise 的链式调用完美解决了回调地狱错误可以被最尾端的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 langzh-CN head meta charsetUTF-8 titleasync/await 数据请求/title /head body h3请求结果/h3 pre idresult/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 langzh-CN head meta charsetUTF-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 classconverter h2汇率转换器/h2 !-- 输入要转换的金额 -- input typenumber idamount placeholder请输入金额 value1 !-- 原始货币下拉框 -- select idfromCurrency/select span→/span !-- 目标货币下拉框 -- select idtoCurrency/select !-- 转换按钮 -- button idconvertBtn立即转换/button !-- 结果显示区域 -- p idresult/p div classtip实时汇率来源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/await、fetch、DOM 操作和事件监听体现了真实项目的开发流程。总结 我们从回调函数讲到 Promise 再到 async/await这是现代 JavaScript 异步编程的主线。掌握了 Fetch API你就打开了与服务器通信的大门。事件循环的知识帮助你写出更可靠、更高效的代码。此刻你已具备了前后端交互的核心技能。接下来我们将进行最后的拼图面向对象、模块化并运用全套知识打造一个大型项目——我的任务管家。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。动手练习是掌握编程最快的方法请务必亲手敲一遍本文的所有示例代码并截图保存你的成果。你的关注是我坚持原创和细节共享的力量来源谢谢大家。