欢迎光临
我们一直在努力

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/await、fetch、DOM 操作和事件监听,体现了真实项目的开发流程。


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


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

赞(0)
未经允许不得转载:171主机测评 » JavaScript 从零基础到精通系列:异步编程与网络请求
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址