一、异步问题
1.案例一 :模拟对用户信息的操作(注意:先获取用户列表后再对用户列表进行操作)
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function getUserMeaasgeList() {
// 发起请求获取用户列表
axios.get(`http://1.12.253.99:3007/api/getUserMessage`).then(function(res) {
// 打印用户列表数据
console.log(res.data);
})
console.log('对获取的用户列表进行操作')
}
getUserMeaasgeList()
</script>
按照代码的正常执行顺序(应该是先执行axios里面的回调函数打印出‘用户列表数据‘后再打印‘对获取的用户列表进行操作’)
接下来看代码运行:
可以看到代码是先执行打印‘对获取的用户列表进行操作’后再执行axios里面的回调函数打印出‘用户列表数据‘
①引起的问题:
用户列表没有获取到就对用户列表进行操作,在项目中代码会报错等,也不符合我们的项目业务需求(比如应该先购买商品后再生成订单等有顺序关系的逻辑)
②为什么代码执行顺序不对?
原因:实际上,js在执行过程中,每遇到一个异步函数,为了不影响主线程的整体代码运行时间(定时器函数比较耗费时间),都会将这个异步函数放入一个异步队列中,只有当主线程执行结束之后,才会开始执行异步队列中的函数。
典型的异步函数有:定时器的回调函数,axios和ajax的回调函数等
③代码实际运行
案例二:回调地狱问题
可能大家会想:把业务逻辑代码都放一个回调函数中处理 ,就比如
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function getUserMeaasgeList() {
// 发起请求获取用户列表
axios.get(`http://1.12.253.99:3007/api/getUserMessage`).then(function(res) {
// 打印用户列表数据
console.log(res.data);
console.log('对获取的用户列表进行操作')
})
}
getUserMeaasgeList()
</script>
①这样确实可以解决异步问题,但是这样会形成回调地狱问题
②试想一下,如果我在回调函数里面还要执行回调函数呢?
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function getUserMeaasgeList() {
// 发起请求获取用户列表
axios.get(`http://1.12.253.99:3007/api/getUserMessage`).then(function(res) {
// 打印用户列表数据
console.log(res.data);
// 对获取的用户列表进行操作
axios.get(`...`).then(function(res) {
// 对获取的用户列表再进行操作
axios.get(`...`).then(function(res) {
// 对获取的用户列表再再进行操作
axios.get(`...`).then(function(res) {
// 对获取的用户列表再再再进行操作
axios.get(`...`).then(function(res) {})
})
})
})
})
}
getUserMeaasgeList()
</script>
可以看到在回调函数里面在执行回调函数,代码整体的维护性,可读性都变的极差,如果出了bug,修复的排查过程也变的极为困难,这个便是所谓的 回调函数地狱。
二、用async和await解决js的异步问题和回调地狱问题
ES2017 标准引入了 async 和await关键字,使得异步操作跟同步操作一样,变得更加方便,这也是解决js异步问题和回调地狱的最佳解决方案。
1. async关键字
①作为一个关键字放到函数前面,用于表示函数是一个异步函数,async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行
async function timeout() {
return 'hello world'
}
timeout();
console.log('虽然在后面,但是我先执行');
打开控制台运行:
可以看到 :async 函数 timeout 调用了,但是没有任何输出, 看一看timeout()执行返回了什么? 把上面的 timeout() 语句改为console.log(timeout())
async function timeout() {
return 'hello world'
}
console.log(timeout());
console.log('虽然在后面,但是我先执行');
再次运行打开控制台:
可以看出:
②async函数的返回值为promise对象,如果要获取到promise 返回值,应该用then 方法
继续修改代码:
async function timeout() {
return 'hello world'
}
timeout().then(result => {
console.log(result);
})
console.log('虽然在后面,但是我先执行');
运行代码控制台输出:
③async返回的promise对象的结果值由async函数执行的返回值决定
1.如果返回的是一个非Promise的对象,则fn()返回的结果就是成功状态的Promise对象,内部会调用Promise.solve() 方法把它转化成一个promise 对象作为返回,值为返回值,想获取返回值就调用then方法获取
<script>
async function fn() {
return 'async函数'
}
console.log(fn());
</script>
2.如果返回的是一个Promise对象,则fn()返回的结果与内部Promise对象的结果一致 ,想获取返回值就调用then方法获取
①返回的是一个Promise对象为成功状态,则函数fn返回结果内部Promise对象的结果一致
<script>
async function fn() {
return new Promise((resolve, reject) => {
resolve('成功的数据')
})
}
console.log(fn());
</script>
控制台运行:
② 返回的是一个Promise对象为失败状态,则函数fn返回结果内部Promise对象的结果一致
<script>
async function fn() {
return new Promise((resolve, reject) => {
reject('失败的数据')
})
}
console.log(fn());
</script>
控制台运行
④.如果函数内部抛出错误,就会调用Promise.reject() 返回一个失败状态的Promise对象
<script>
async function fn() {
throw '出异常问题了'
}
console.log(fn());
</script>
控制台运行:
⑤.如果函数内部抛出错误, promise 对象有一个catch 方法进行捕获。
<script>
async function fn() {
throw '出异常问题了'
}
fn().catch(err => {
console.log(err);
})
</script>
控制台运行
2.async总结
①作为一个关键字放到函数前面,用于表示函数是一个异步函数,async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行
②async函数的返回值为promise对象(拥有promise的全部特性),如果要获取到promise 返回值,应该用then 方法
③async返回的promise对象的结果值由async函数执行的返回值决定
3.await关键字
①await必须放在async函数中
②await右侧的表达式可以放任何表达式,一般为promise对象
③await可以返回的是右侧promise成功的值
④await右侧的promise如果失败了,就会抛出异常,需要通过catch捕获处理
⑤await的意思是等待的意思,当主线程执行到await后面的表达式的时候,主线程会等待await后面的表达式运行结束后再继续运行主线程的其他代码(完美解决异步问题)
1.promise状态为成功,await 的返回值为promise成功的值
1. // 1创建promise对象
const p = new Promise((resolve, rejiect) => {
resolve('成功!')
});
// await 必须在async函数里面
async function main() {
//result接受promise成功的返回值
let result = await p;
console.log(result);//成功
}
main();
2.promise状态为失败,通过catch捕获处理
<script>
1. // 1创建promise对象
const p = new Promise((resolve, rejiect) => {
rejiect('失败!')
});
// await 必须在async函数里面
async function main() {
let result = await p.catch(err => {
捕获失败的结果//
console.log(err);//失败
});
console.log(result);
}
main();
</script>
三、解决本文章的异步问题和回调地狱问题
1.异步问题:(axios方法的返回值就是promise对象,所以可以用async和await来处理)
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
async function getUserMeaasgeList() {
// 发起请求获取用户列表
const result = await axios.get(`http://1.12.253.99:3007/api/getUserMessage`)
console.log(result.data);
console.log('对获取的用户列表进行操作')
}
getUserMeaasgeList()
</script>
控制台运行:
2.回调地狱问题:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
async function getUserMeaasgeList() {
// 发起请求获取用户列表
const result1 = await axios.get(`http://1.12.253.99:3007/api/getUserMessage`)
//对用户列表进行操作
const result2 = await axios.get(``)
//对用户列表再进行操作
const result3 = await axios.get(``)
//对用户列表再再进行操作
const result4 = await axios.get(``)
}
getUserMeaasgeList()
</script>
四.总结
1.异步问题:实际上,js在执行过程中,每遇到一个异步函数,为了不影响主线程的整体代码运行时间(定时器函数比较耗费时间),都会将这个异步函数放入一个异步队列中,只有当同步线程执行结束之后,才会开始执行异步队列中的函数。
典型的异步函数有:定时器的回调函数,axios和ajax的回调函数等