# JS异步解决方案发展以及优缺点
上面提到了Async/await的原理,那就一定要提提js关于异步解决方案的发展之路了。
# 1)callback回调
ajax('XXX1', () => {
// callback 函数体
ajax('XXX2', () => {
// callback 函数体
ajax('XXX3', () => {
// callback 函数体
})
})
})
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
回调地狱的根本问题在于:
- 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
- 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
- 嵌套函数过多的多话,很难处理错误
# 2)Promise
Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})
- 优点:比一层层的回调函数写法优雅
- 缺点:无法取消 Promise ,错误需要通过回调函数来捕获
# 3)Generator
可以控制函数的执行,可以配合 co 函数库使用
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
# 4)Async/await
- 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
- 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
有一个小demo可以看一下:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
输出如下:
// 1 1
// 2 10
因为await是异步操作,所以会先执行同步代码,所有会得到以上的执行结果。
【扩展-async/await的执行顺序】
很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。等本轮事件循环执行完了之后又会跳回到async函数中等待await后面表达式的返回值,如果返回值为非promise则继续执行async函数后面的代码,否则将返回的promise放入promise队列(Promise的Job Queue)
先看下面的代码:
function test111() {
console.log("执行111");
return "111 end"
}
function test222() {
console.log("执行222");
return "222 end"
}
async function test() {
console.log("test start...");
const v1 = await test111();
console.log(v1);
const v2 = await test222();
console.log(v2);
console.log(v1, v2);
}
test();
const promise = new Promise((resolve)=> {
console.log("promise start..");
resolve("promise resolve");
});
promise.then(val=> console.log(val));
console.log("test end...")
// test start...
// 执行111
// promise start..
// test end...
// 111 end
// 执行222
// promise resolve
// 222 end
// 111 end 222 end
可以看到,代码执行顺序如下:
- 首先执行
test()
函数,打印"test start..." - 当test函数执行到
const v1 = await test()
的时候,会先执行test111
这个函数打印出“执行test111”的字符串,然后因为await会让出线程就会区执行后面的代码(即promise) - 然后打印出“promise start..”接下来会把返回的这promise放入promise队列(Promise的Job Queue),继续执行打印“test end...”
- 等本轮事件循环执行结束后,又会跳回到async函数中(test函数),等待之前await 后面表达式的返回值,因为
test111
不是async函数,返回的是一个字符串“111 end”,test函数继续执行 - 执行到
const v2 = await test222()
中的test222()
函数,和之前一样又会跳出test函数,执行后续代码,此时事件循环就到了promise的队列,执行promise.then((val)=> console.log(val));then后面的语句,之后和前面一样又跳回到test函数继续执行 - 再执行
console.log(v2);
打印v1,即打印"222 end" - 最后再执行
console.log(v1, v2)
,打印"111 end 222 end"
我们将题目稍作改动,原有的test111、test222函数变为了async,返回值变为了一个Promise:
async function test111() {
console.log("执行111");
return Promise.resolve("111 end");
}
async function test222() {
console.log("执行222");
return Promise.resolve("222 end");
}
async function test() {
console.log("test start...");
const v1 = await test111();
console.log(v1);
const v2 = await test222();
console.log(v2);
console.log(v1, v2);
}
test();
const promise = new Promise((resolve)=> {
console.log("promise start..");
resolve("promise resolve");
});
promise.then(val=> console.log(val));
console.log("test end...")
// test start...
// 执行111
// promise start..
// test end...
// promise resolve
// 111 end
// 执行222
// 222 end
// 111 end 222 end
这里我们发现,打印promise resolve
提到111 end
之前了,其他执行顺序没发生变化。原因是因为现在函数加了async,返回的是一个Promise对象要要等它resolve,所以将当前Promise推入队列,所以会继续跳出test函数执行后续代码。之后就开始执行promise的任务队列了。