背景
异步函数:各自执行各自的,互不干扰,互相之间也不会等待。
假设有三个人A, B, C一起接力跑,需要A跑到B的位置,B才能开始跑,B跑到C的位置,C才能开始跑
遇到多个异步函数需要按顺序执行该怎么做呢?
一、回调函数(回调地狱)
缺点:多个回调函数嵌套的时候会造成回调函数地狱,代码耦合度高,不直观,可维护差。
示例:
function run(next) {
setTimeout(() => {
console.log('跑完一段')
next();
}, 6000);
}
run(function() {
run(function(){
setTimeout(() => {
console.log('到终点啦!')
}, 6000);
});
})
如上,任务多起来旧会形成很深的嵌套结构——称之回调地狱。
二、Promise链式调用
Promise 是异步编程的一种解决方案,避免了地狱回调,将嵌套的回调函数作为链式调用
2.1 步骤
1). 定义前一项任务时
返回一个promise:return new Promise()
,并包裹原异步任务
function run(){
return new Promise((resolve) => {
// 原异步任务
setTimeout(() => {
// 在原异步函数的最后一行代码,主动调用resolve
resolve()
}, 6000);
}
)
}
2). 使用.then连接前后两个异步任务:
任务1().then(任务2).then(任务3)
promise解决回调地狱代码:
function run(isFinal){
return new Promise((resolve) => {
setTimeout(() => {
console.log(isFinal ? '到终点啦!' : '跑完一段')
resolve()
}, 6000);
}
)
}
run()
.then(run)
.then(run(isFinal))
跑完一段
跑完一段
到终点啦!
2.2 Promise
a. promise参数
new Promise自带两个参数: .
- resolve——通往
.then
- reject——通往
.catch
前一项任务()
.then(下一项任务)
.then(...)
.catch(
function(错误提示信息){
错误处理代码
}
)
示例:
function checkLogin(username, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (username === 'admin' && password === '123456') {
resolve('登录成功'); // 成功时调用
} else {
reject('用户名或密码错误'); // 失败时调用
}
}, 1000);
});
}
checkLogin('admin', '123456')
.then(message => {
console.log(message); // 成功时输出:"登录成功"
})
.catch(error => {
console.log(error); // 输出:"用户名或密码错误"
});
b. 实例的三大状态和两个过程
- 当异步任务执行过程中, pending(挂起)
- 当异步任务成功执行完,通过resolve() 和reject()改变状态:
- 调用
Resolve()
,切换为 fulfilled(已完成) 状态,new Promise()会自动调用.then()
执行下一项任务 - 调用
reject()
,切换为 rejected(已拒绝) 状态,new Promise()会自动调用.catch()
执行错误处理代码
- 调用
两个过程
- pending -> fulfilled:Resolved
- pending -> rejected:Rejected
一旦状态改变,就不会再变,任何时候都可以得到这个结果
判断题:对于一个向后台获取数据已经产生结果的promise:p1,再次调用p1.then,不会去重新发起请求获取数据 √
2.3 异步任务传参
a. 单个传参
利用resolve()进行传参,resolve默认只能传一个变量
示例:
function fetchData(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`用户${userId}的数据`);
}, 1000);
});
}
// 使用
fetchData(123)
.then(data => {
console.log(data); // "用户123的数据"
});
b. 多个传参
那么如果我们想要传递多个参数呢?
可以放在数组或对象中传递:
示例:
function getUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, name: `用户${id}` }), 500);
});
}
function getPosts(user) {
return new Promise(resolve => {
setTimeout(() => resolve(`${user.name}的文章列表`), 500);
});
}
getUser(123)
.then(user => {
console.log(user); // { id: 123, name: "用户123" }
return getPosts(user); // 传递user对象
})
.then(posts => {
console.log(posts); // "用户123的文章列表"
});
2.4 Promise.all()
- 并行执行:所有Promise同时执行
- 全部成功:只有当所有Promise都resolve时才会进入.then()
- 失败处理:任意一个Promise reject就会立即进入.catch()
- 结果顺序:结果数组顺序与输入Promise数组顺序一致
function fetchData(id, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(`数据${id}`), delay);
});
}
const request1 = fetchData(1, 1000); // 1秒后返回
const request2 = fetchData(2, 1500); // 1.5秒后返回
const request3 = fetchData(3, 500); // 0.5秒后返回
Promise.all([request1, request2, request3])
.then(results => {
console.log('所有请求完成:', results);
// 输出: ["数据1", "数据2", "数据3"] (约1.5秒后同时显示)
})
.catch(error => {
console.log('请求失败:', error);
});
2.5 Promise.race()
- 竞速机制:只关心最先完成的Promise
- 短路特性:只要有一个Promise完成(无论resolve/reject)就立即返回
- 结果类型:返回第一个完成的Promise的结果或错误
function fetchData(source, delay) {
return new Promise(resolve => {
setTimeout(() => resolve(`${source}数据`), delay);
});
}
const apiRequest = fetchData("API", 800);
const cacheRequest = fetchData("缓存", 200);
const backupRequest = fetchData("备用服务", 1200);
Promise.race([apiRequest, cacheRequest, backupRequest])
.then(firstResponse => {
console.log("最先返回的结果:", firstResponse);
// 输出: "缓存数据" (约200ms后)
})
.catch(error => {
console.log("错误:", error);
});
三、generator
当遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权给转移回来。
核心:考虑何时将函数的控制权转移回来
详见:ES6——generator与yield
四、async/await
详见:事件循环机制 + ES7:Async/Await(基于generator原理实现)附详细示例分析
async 基于Generator+ promise 实现的一个自动执行的语法糖,为了进一步优化then链而出现。
async 是“异步”,申明一个函数是异步的,await 则为等待,当执行到一个await语句的时候,如果语句返回一个promise 对象,那么函数将会等待promise 对象的状态变为resolve 后再继续向下执行。
(参照generator思想)
generator和async/await区别:
- async/await自带执行器,不需要手动调用next()就能自动执行下一步
- async函数返回值是Promise对象,而Generator返回的是生成器对象
- await能够返回Promise的resolve/reject的值
async函数返回的是一个Promise对象
例如,async的函数会在这里帮我们隐式使用Promise.resolve(1)
:
async function test() {
return 1
}
等价于
function test() {
return new Promise(function(resolve, reject) {
resolve(1)
})
}
优点: 类同步代码
传统 Promise 写法:
function getUser(userId) {
return fetch(`/users/${userId}`);
}
function getOrders(userId) {
return fetch(`/orders?user=${userId}`);
}
// 嵌套的 .then() 回调
getUser(123)
.then(user => {
return getOrders(user.id)
.then(orders => {
console.log('用户:', user);
console.log('订单:', orders);
});
})
.catch(error => {
console.log('出错:', error);
});
使用 async/await
改进后:
async function displayUserData() {
try {
const user = await getUser(123); // 等待用户数据
const orders = await getOrders(user.id); // 用用户ID等订单数据
console.log('用户:', user); // 像同步代码一样写
console.log('订单:', orders); // 层级保持扁平
} catch (error) {
console.log('出错:', error); // 统一错误处理
}
}
displayUserData();
- 调试友好
- 错误处理更简单
用 try/catch 即可捕获所有错误(包括同步错误) - 逻辑更直观
消除了 .then() 嵌套,顺序执行异步操作,像同步代码一样自然 - 轻松传值
Promise 传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法
总结
promise
、async/await
专门用于处理异步操作;generator
并不是,他还有其他功能(对象迭代,部署Interator接口等)promise
编写代码比async/await
复杂,可读性差async
为genarator语法糖,自动执行,使用上最简洁,同步编写代码,是异步编程的最终方案