JS 异步解决方案的发展历程以及优缺点:回调函数、Promise、Generator、Async/await
回调函数
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队 等着,会拖延整个程序的执行。)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return;(多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦 合度太高,不利于代码的可维护。不能用 try catch 捕获错误,不能 return)
ajax('XXX1', () => {
// callback 函数体ajax('XXX2', () => {
// callback 函数体ajax('XXX3', () => {
// callback 函数体
})
})
})
回调地狱的根本问题在于:
缺乏顺序性:回调地狱导致的调试困难,和大脑的思维方式不符
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
嵌套函数过多的多话,很难处理错误
Promise
优点:解决了回调地狱的问题;
ajax('XXX1')
.then(res => {
// 操作逻辑returnajax('XXX2')
}).then(res => {
// 操作逻辑returnajax('XXX3')
}).then(res => {
// 操作逻辑
}).catch(err => {
// 错误处理
})
缺点:无法取消 Promise,错误需要通过回调函数来捕
Generator
特点:可以控制函数的执行。(它可以在遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕时再将执行权 给转移回来。因此在 generator 内部对于异步操作的方式,可以以 同步的顺序来书写。使用这种方式需要考虑的问题是何时将函数的控 制权转移回来,因此需要有一个自动执行 generator 的机制,比如 说 co 模块等方式来实现 generator 的自动执行。)
function* getResult() {
yield new Promise((resolve, reject)=>{
setTimeout(() => {
resolve(1)
console.log(1);
}, 1000);
})
yield new Promise((resolve, reject)=>{
setTimeout(() => {
resolve(2)
console.log(2);
}, 500);
})
yield new Promise((resolve, reject)=>{
setTimeout(() => {
resolve(3)
console.log(3);
}, 100);
})
}
const gen = getResult()
// gen.next() = {value: yield 返回的数据, done: 迭代器是否走完}
// gen.next().value 就是每一次 yield 之后返回的 Promise
// 递归封装
function co(g) {
const newObj = g.next()
// 递归停止条件: 当迭代器到最后一个yield
if (newObj.done) {
return
}
nextObj.value.then(()=>{
co(g)
})
}
co(gen)
Async/Await
原理:函数是 generator 和 promise 实现的 一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将 会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此 可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动 执行。
优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低
async functiontest() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1