努力到无能为力,拼搏到感动自己!
当你面对层层嵌套的回调、无法定位的异步错误时,是否想过: 为什么处理异步这么痛苦?本节课将带你穿越时空,经历 JS 异步编程的进化史,最后用 Async/Await 开发「天气查询工具」,体验丝滑的异步代码!
一、异步编程进化三部曲
1. 回调地狱(Callback Hell)
getUser(userId, function(user) {
getOrders(user.id, function(orders) {
getItems(orders[0].id, function(items) {
render(items, function() {
// 更多嵌套...
});
});
});
});
痛点:
- 金字塔缩进,可读性差
- 错误处理分散,难以维护
- 无法并行/顺序控制
2. 蒸汽时代:Promise 链式调用
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getItems(orders[0].id))
.then(render)
.catch(error => console.error("全局捕获:", error));
优势:
- 链式结构,线性表达异步流程
- 统一错误处理
- 支持 Promise.all 等高级操作
3. 电气时代:Async/Await 终极方案
async function loadData() {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const items = await getItems(orders[0].id);
await render(items);
} catch (error) {
console.error("加载失败:", error);
}
}
优势:
- 同步写法写异步,心智负担低
- 配合 try/catch 实现优雅错误处理
- 代码可调试性大幅提升
二、Promise 核心机制精讲
1. 三种状态与链式传递
状态: pending → fulfilled/rejected(不可逆)
链式原理: .then 返回新 Promise,可连续调用
2. 创建 Promise
function fetchData(url) {
return new Promise((resolve, reject) => {
axios.get(url)
.then(res => resolve(res.data))
.catch(err => reject(err));
});
}
3. 并发控制
// 并行请求,全部成功
Promise.all([api1, api2, api3])
.then(([res1, res2, res3]) => {});
// 竞速模式,首个成功即返回
Promise.race([api1, api2])
.then(firstResult => {});
三、Async/Await 实战技巧
1. 错误处理标准化
// 封装通用错误处理
async function safeRequest(url) {
try {
const res = await fetch(url);
return await res.json();
} catch (error) {
reportError(error); // 统一上报日志
throw new Error("请求失败,请重试");
}
}
2. 并行请求优化
// 顺序请求(慢)
const user = await getUser();
const orders = await getOrders(user.id);
// 并行请求(快)
const [user, orders] = await Promise.all([
getUser(),
getOrders(user.id) // 假设已知user.id
]);
3. 突破循环限制
// for循环中使用await
async function processArray(array) {
for (const item of array) {
await processItem(item); // 顺序执行
}
}
// 并行处理所有元素
await Promise.all(array.map(item => processItem(item)));
四、实战:天气查询小工具
1. 功能需求
- 输入城市名获取实时天气
- 显示温度、天气图标、风速
- 错误提示(如城市不存在)
2. 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/qweather-icons@1.3.0/font/qweather-icons.css">
<title>天气查询</title>
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #f0f8ff;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
color: #333;
}
h1 {
font-size: 2.5em;
margin-bottom: 20px;
color: #4CAF50;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.search-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
input[type="text"] {
padding: 15px;
font-size: 18px;
border: 2px solid #ccc;
border-radius: 10px;
width: 240px;
margin-right: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
button {
padding: 15px 30px;
font-size: 18px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease;
}
button:hover {
background-color: #45a049;
}
.weather-card {
background: linear-gradient(135deg, #64b6ac, #5dbcd2);
color: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
text-align: left;
margin-top: 20px;
width: 350px;
animation: fadeIn 0.5s ease;
}
.weather-card i {
font-size: 30px;
margin-left: 10px;
}
.weather-card p {
margin: 5px 0;
font-size: 1.2em;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
.error {
color: red;
font-size: 1.2em;
margin-top: 20px;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
</head>
<body>
<h1>天气查询</h1>
<div class="search-container">
<input type="text" id="cityInput" placeholder="输入城市id(例如:北京-101010100)">
<button onclick="getWeather()">查询</button>
</div>
<div id="weatherResult"></div>
<script>
const API_KEY = '772e2fb170e64febb119c74c71d65fd1'; // 和风天气API Key
const BASE_URL = 'https://devapi.qweather.com/v7/weather/now'; // 和风天气API的基础URL
async function getWeather() {
const city = document.getElementById('cityInput').value; //101010100 北京
if (!city) return;
try {
const url = `${BASE_URL}?location=${city}&key=${API_KEY}`
const response = await fetch(url);
const data = await response.json();
if (data.code !== "200") throw new Error(data.message);
displayWeather({
temp: data.now.temp,
icon: data.now.icon,
text: data.now.text,
wind: data.now.windDir,
humidity: data.now.humidity,
pressure: data.now.pressure
});
} catch (error) {
showError(error.message);
}
}
function displayWeather({ temp, icon, text, wind, humidity, pressure }) {
const html = `
<div class="weather-card">
<i class="qi-${icon}"></i>
<p>温度:${temp}℃</p>
<p>天气:${text}</p>
<p>风向:${wind}</p>
<p>湿度:${humidity}%</p>
<p>气压:${pressure} hPa</p>
</div>
`;
document.getElementById('weatherResult').innerHTML = html;
}
function showError(msg) {
document.getElementById('weatherResult').innerHTML =
`<p class="error">${msg}</p>`;
}
</script>
</body>
</html>
3. 代码亮点
- 异步流程清晰:用 Async/Await 替代回调嵌套
- 解构赋值优化:直接提取嵌套数据
- 错误处理集中:try/catch 捕获所有异常
下节预告
第 11 课:小白进阶必看!JavaScript 模块化与工程化——从脚本到项目
- ES Modules 标准详解
- Webpack 核心配置
- 实战:将天气工具工程化
回复【JS】获取本课源码+工具包!