JS 异步解决方案的发展历程以及优缺点:回调函数、Promise、Generator、Async/await

1、回调函数(callback)

  • 优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队 等着,会拖延整个程序的执行。)

setTimeout(() => {
    // callback 函数体
}, 1000)
  • 缺点:多个回调函数嵌套的时候会造成回调函数地狱,上下两层的回调函数间的代码耦 合度太高,不利于代码的可维护。不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:
缺乏顺序性:回调地狱导致的调试困难,和大脑的思维方式不符
嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
嵌套函数过多的多话,很难处理错误
ajax('XXX1', () => {
    // callback 函数体ajax('XXX2', () => {
        // callback 函数体ajax('XXX3', () => {
            // callback 函数体
        })
    })
})

2、Promise

  • 优点:将嵌套的回调函数作为链式调用,解决了回调地狱的问题

ajax('XXX1')
  .then(res => {
      // 操作逻辑returnajax('XXX2')
  }).then(res => {
      // 操作逻辑returnajax('XXX3')
  }).then(res => {
      // 操作逻辑
  }).catch(err => {
      // 错误处理
  })
  • 缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3、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)

4、Async/await

  • async 函数是 generator 和 promise 实现的 一个自动执行的语法糖,它内部自带执行器,当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将 会等待 promise 对象的状态变为 resolve 后再继续向下执行。因此 可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动 执行。

  • 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题

  • 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

async functiontest() {
  // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式// 如果有依赖性的话,其实就是解决回调地狱的例子了
  await fetch('XXX1')
  await fetch('XXX2')
  await fetch('XXX3')
}

下面来看一个使用 await 的例子:

let a = 0
let b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1

对于以上代码你可能会有疑惑,让我来解释下原因

  • 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来

  • 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码

  • 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10

上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值