在JS的异步世界里,Promise和async/await就像咖啡和咖啡因,一个是实体,一个是更便捷的摄入方式
首先,想象一下你去咖啡店点单:
Promise是服务员给了你一张排队的小票(它就是Promise的then方法),说咖啡好了会震动提醒你,
而async/await是VIP通道,服务员会当场原地给你做好之后直接递给你
前者是让你在等待时还能刷会儿手机,后者就是让你代码同步的爽感
很多人以为async/await是Promise的替代品,其实这个说法是错误的。async/await的本质是Promise的语法糖,他的底层依然是用Promise去实现的,async/await会让异步代码看起来更像同步代码
在写法上来说:
Promise写法
fetchData().then(res => {
return fetchNewData(res)
}).then(result => {
console.log(result)
}).catch(err => {
console.log(err)
})
async/await写法
async function handleData(){
try{
const data = await fetchData()
const result = await fetchNewData(data)
console.log(result)
}catch(err) {
console.log(err)
}
}
从这里看,其实两者的功能是完全一致的,但是对于async/await这样的写法来说,看起来会更符合人的阅读习惯。
那么我们针对这两种情况,各自的使用时机是什么呢?
对于线性异步流程来说
就是一共需要请求三次,每次都会使用上一次返回的结果作为参数去请求下一次
比如:用户登录后获取token,再通过token获取用户信息,在根据用户信息去获取对应的角色权限
async function getApp(username, password) {
try{
const token = await login(username, password)
const userInfo = await getUserInfo(token)
const permissions = await getPermissions(userInfo.role)
render(permissions)
} catch(err) {
console.log(err)
}
}
对于包含条件判断的异步逻辑的话
async function checkAndUpdateAuth() {
const appVersion = await getAppVersion()
if(appVersion === '1.8'){
const newInfo = await getNewInfo()
if(newInfo.force){
await updateInfo()
} else {
await isUpdateInfo()
}
}
}
对于需要中断执行的场景来说
async function submitForm(data){
if(!data.password) {
showError('密码不能为空')
return
}
try{
const validateResult = await validateForm(data)
if(validateResult.errno !== 0) {
showError(validateResult.errmsg)
return
}
await submitForm(data)
} catch(err) {
handleError(err)
}
}
虽然使用async/await会方便一些,但是在有些场景下,使用Promise的原生API会更适合一点
当需要并行执行多个异步操作时
当我们需要执行多个异步任务,并且这些异步任务互相不依赖时,我们使用Promise.all并行执行能够大幅的提高效率,比如我们要同时获取列表数据以及筛选项的状态枚举
async function loadDashBoard() {
const [list, categories] = await Promise.all([
fetchList()
fetchCategories()
])
renderList(list)
renderFilters(categories)
}
如果使用await的话,就变成串行执行了,总耗时就会增加
const list = await fetchList()
const categories = await fetchCategories()
当我们需要超时控制的异步操作时
Promise.race方法会使用最采用最先改变状态的Promise结果,谁先成功/失败就采用谁的结果,所以可以用来实现,如果一个结果三秒内没返回结果就提示请求超时。
function withTimeout(promise, timeoutMs = 3000) {
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error('请求超时'));
}, timeoutMs);
});
return Promise.race([
promise,
timeoutPromise
]).finally(() => clearTimeout(timer));
}
async function loadData() {
try {
const data = await withTimeout(fetchLargeData());
render(data);
} catch (err) {
showError(err.message);
}
}
再一次就是当我们处理动态数量的异步任务时
比如我们批量上传文件时
async function uploadFiles(files) {
if (!files?.length) return;
const uploadPromises = files.map(file => {
return uploadFile(file);
});
const results = await Promise.all(uploadPromises);
const successCount = results.filter(r => r?.success).length;
showMessage(`成功上传 ${successCount}/${files.length} 个文件`);
}
在这些动态场景下,Promise的API会比async/await更高效
而在实际开发中,对于两者通常采用结合使用的方式,会带来更好的效果。比如先并行获取枚举数据,然后再串行处理依赖关系
async function buildReport() {
const [users, orders, products] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchProducts()
]);
const userStats = await calculateUserStats(users);
const orderSummary = await generateOrderSummary(orders, userStats);
const report = await compileReport(orderSummary, products);
return report;
}
这段代码先用 Promise.all 并行请求,节省时间;再用 async/await 处理有依赖的串行逻辑,兼顾效率和可读性。
所以我们具体问题具体分析,具体实践
针对不同的问题去采用不同的处理方式:
对于顺序操作的异步任务通常更采用async/await的方式
而对于批量解决或者是进行超时控制时,采用Promise的API会更好一点