放弃 .then() 链?用 async/await 重构异步代码的真实体验
在 JavaScript 中,处理异步操作(例如网络请求、文件读写等)是常见的需求。.then() 写法和 async/await 写法是处理这些异步操作的两种主要方式,它们都基于 Promise 对象。其中,async/await 是在 ES2017 (ES8) 中引入的语法糖,旨在让异步代码的编写和阅读更像是同步代码。
1. .then() 写法
.then()
写法使用回调函数和链式调用,解决了回调地狱问题。
链式调用:每次调用 .then()
都会返回一个新的 Promise 对象,这使得我们可以将多个异步操作串联起来,形成一个链条。
调用 .then()
后函数会继续执行,不会阻塞后续代码。回调函数会在 Promse 完成后被放入任务队列执行。这句话什么意思呢?看下面解析:
现在有一个典型的场景:如果说现在有个 Echarts 图表需要后端返回数据
const getList = () => {
let data: any[] = []
axios.get("/getList").then((res) => {
console.log(res.data);
data = res.data || []
});
// 然后是下面的其余代码,绘制图表什么的....
const option = {
xAxis: {
type: "category",
data: data,
axisLabel: { color: "#4a8ab9", fontSize: 9 },
axisLine: { lineStyle: { color: "#21b8da", width: 0.5 } }
},
}
}
data = res.data || []
这里你以为是赋值上了吧!!!不!!!并没有!!!
走到这里的时候,其实它只是把这个代码交给其他线程处理了,然后直接往下执行,所以说,还没等赋值 data = res.data || []
呢,你的代码就已经 data: data
赋值了,所以说页面应该会看不到数据的。
详细分解一下代码的执行顺序:
- let data: any[] = []: 初始化一个空数组 data。
- axios.get("/getList"): 发起一个网络请求。这是一个异步操作。JavaScript 引擎不会在这里傻等,它会把这个任务交给浏览器(或 Node.js 环境)去处理,然后立即继续执行下一行代码。
- .then((res) => { ... });: 为这个网络请求的 Promise 注册一个“成功后的回调函数”。这个回调函数现在不会执行,它被放入了一个任务队列,等待网络请求成功返回后才会被执行。
- const option = { ... }: 代码立即执行到这里。此时,第 2 步的网络请求很可能还在进行中,第 3 步注册的回调函数也自然还没被调用。所以,这里的 data 变量仍然是第 1 步中定义的那个空数组 []。
- getList 函数执行完毕。
- (过了一段时间后...): 网络请求成功,服务器返回了数据。
- 执行回调函数: JavaScript 引擎从任务队列中取出之前注册的回调函数 (res) => { data = res.data || [] } 并执行它。此时,data 变量才被赋予了从后端获取的值。但为时已晚,包含空数据的 option 对象早已经创建好了。
下面代码才是正确写法:
// 步骤1:改造数据获取函数,让它返回 Promise
const getList = () => {
// return 出去整个 axios 调用,它本身就是一个 Promise
return axios.get("/getList");
};
// 步骤2:在调用处使用 .then() 来衔接后续操作
const initChart = () => {
console.log("开始初始化图表...");
getList()
.then(res => {
// 第一个 .then() 负责从响应中提取和处理数据
console.log("数据请求成功,正在处理数据...");
const data = res.data || [];
return data; // 将处理好的数据 return 出去,传递给下一个 .then()
})
.then(data => {
// 第二个 .then() 接收上一步返回的 data,并执行绘图逻辑
console.log("数据处理完毕,准备绘制图表:", data);
const option = {
xAxis: {
type: "category",
data: data, // <-- 这里接收到的 data 就是有值的
axisLabel: { color: "#4a8ab9", fontSize: 9 },
axisLine: { lineStyle: { color: "#21b8da", width: 0.5 } }
},
// ...
};
// 调用绘图
drawChart(option);
})
.catch(error => {
// .catch() 用于捕获链条中任何环节出现的错误
console.error("在初始化图表过程中发生错误:", error);
});
};
// 调用初始化函数
initChart();
所以说 .then()
应该 是这样的:
// 使用 .then() 处理异步操作
axios.get("/getList")
.then(result => {
console.log(result); // 输出: 数据获取成功!
// 返回一个新的值,供下一个 .then() 使用
return result + " (第一次处理)";
})
.then(result2 => {
console.log(result2); // 输出: 数据获取成功! (第一次处理)
// 也可以返回一个新的 Promise
return new Promise(resolve => {
setTimeout(() => {
resolve(result2 + " (第二次处理)");
}, 500);
});
})
.then(result3 => {
console.log(result3); // 输出: 数据获取成功! (第一次处理) (第二次处理)
})
.catch(error => {
// 如果链条中任何一个 Promise 失败,就会被 .catch() 捕获
console.error(error);
});
2. async/await 写法
async/await 是构建在 Promise 之上的一种更现代、更简洁的语法。
async 函数
:在函数声明前加上 async 关键字,表明这是一个异步函数。异步函数会隐式地返回一个 Promise。
await 操作符
:await 只能在 async 函数内部使用。它会暂停 async 函数的执行,等待其后的 Promise 对象的状态变为 settled(无论是成功还是失败),然后恢复执行,并返回 Promise 的结果。
// 使用 async/await 处理异步操作
async function main() {
try {
const result = await axios.get("/getList");
console.log(result); // 输出: 数据获取成功!
const result2 = result + " (第一次处理)";
console.log(result2); // 输出: 数据获取成功! (第一次处理)
const result3 = await processData2(result2);
console.log(result3); // 输出: 数据获取成功! (第一次处理) (第二次处理)
} catch (error) {
// 如果 await 等待的 Promise 失败,错误会被 try...catch 捕获
console.error(error);
}
}
最后,可以看一下上一个问题用 async/await 会变得多么简洁直观,因为它就是为了解决这种“同步化”的思维模式而设计的。
const initChartWithAsync = async () => {
try {
console.log("开始初始化图表 (async/await)...");
// 1. await 会“暂停”函数执行,直到 axios.get() 完成,并返回响应
const res = await axios.get("/getList");
// 2. 代码能走到这一行,说明上面的 await 已经成功了
const data = res.data || [];
console.log("数据获取成功:", data);
// 3. 像写同步代码一样,直接使用 data
const option = {
xAxis: {
type: "category",
data: data,
axisLabel: { color: "#4a8ab9", fontSize: 9 },
axisLine: { lineStyle: { color: "#21b8da", width: 0.5 } }
},
// ...
};
drawChart(option);
} catch (error) {
// 如果 await 的 Promise 失败了,错误会被 catch 捕获
console.error("在初始化图表过程中发生错误:", error);
}
};
// 调用
initChartWithAsync();
更多推荐
所有评论(0)