避免回调地狱的解决方案 async/await:用同步的方式去写异步代码

本文探讨了异步编程的概念,通过实例展示了从传统的回调函数到Generator函数,再到Promise和async/await的演变。Generator通过yield控制执行流程,Promise解决了回调地狱问题,而async/await则提供了更简洁的异步处理方式,使得异步代码看起来接近同步。文章总结了各种异步方法的优缺点,并指出async/await是目前的终极解决方案。
摘要由CSDN通过智能技术生成


前言

这篇文章主要给大家分享一下,自己关于异步编程方面的一些见解,和实际开发项目过程中使用到的一些技术以及技巧


一、引入异步编程

对异步编程编程不太熟悉的小伙伴,可以通过下面这些简单的例子,先建立对异步编程的基本认知,首先作为一名前端程序员,联调接口是我们最熟悉不过的技能。下面这种代码,这样的回调函数,大家肯定是再熟悉不过。

ajax(url, (res) => {
  console.log(res);
})

但是大家是否有想过,如果某一个业务场景比较复杂,需要一个类似链式调用的操作,那么,如果没猜错我相信大多数人的代码,可能会变成这样。

ajax(url, (res) => {
  console.log(res);
  // ...处理代码
  ajax(url2, (res2) => {
    console.log(res2);
    // ...处理代码
    ajax(url3, (res3) => {
      console.log(res3);
      // ...处理代码
      // 若干个回调
    })
  })
})

如果有若干个,当然我相信,现实业务中这种情况肯定是不会出现的,我们只是为了说明这种写法这理论是不严谨,容易进入 回调地狱
回调地狱:
上面那个就是典型的回调地狱,那么肯定有人会问回调地狱会有哪些缺点呢,其实回调地狱不光只是缺点,也还是有些优点的,下面是个人的一些理解。

  • 优点:解决了 同步阻塞 的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)
  • 缺点:回调地狱;不能用 try catch 捕获错误;不能 return

二、常见处理异步编程的几种方式

1.Generator函数

  • 介绍:ES6 新引入了 Generator 函数(生成器函数),可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。最大的特点就是 可以控制函数的执行。
  • Generator和函数不同的是,generator由function *定义(注意多出的星号),并且,除了return语句,还可以用yield返回多次
    利用 yield返回多次这个特性,我们就可以实现有同步的方式写出异步的代码
//菲波那切数列,简单介绍一下它,n=(n-1) + (n-2)
function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

可能有人对[a, b] = [b, a+b],不太理解。其实[a, b] = [b, a+b]起到了var t = a + b;a = b;b = t;的作用,或者说是这段代码的精简版(省略了中间变量t的申请),实际上,它的运算顺序是从右到左的,先计算b和a+b,然后将b的值赋予a,再将a+b的值赋予b,以此来达到预期的计算结果。

next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。
当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。最终我们可以将复杂的回调函数改成这样

try {
    r1 = yield ajax('http://url-1', data1);
    r2 = yield ajax('http://url-2', data2);
    r3 = yield ajax('http://url-3', data3);
    success(r3);
}
catch (err) {
    handle(err);
}

2.Promise函数

这个函数我相信大家都很熟悉,在这里就不过多介绍

3.async/await

async 是一个通过 异步执行并隐式返回 Promise 作为结果的函数。

async function async1() {
  return '测试';
}
console.log(async1());

执行这段代码,可以看到调用 async 声明的 async1 函数返回了一个 Promise 对象,状态是 resolved,返回结果如下所示:Promise {: “测试”}。和 Promise 的链式调用 then 中处理返回值一样。这就完美的解释了,MDN对async的定义,即 异步执行并隐式返回 Promise 作为结果的函数。


await 需要跟 async 搭配使用,结合下面这段代码来看看 await 到底是什么
async function foo() {
  console.log(1)
  let a = await 100
  console.log(a)
  console.log(2)
}
console.log(0)
foo()
console.log(3)

分析:

  1. 首先,执行 console.log(0) 这个语句,打印出来 0。
  2. 紧接着就是执行 foo 函数,由于 foo 函数是被 async 标记过的,所以当进入该函数的时候,JS 引擎会保存当前的调用栈等信息,然后执行 foo 函数中的 console.log(1) 语句,并打印出 1。
  3. 当执行到 await 100 时,会默认创建一个 Promise 对象,进入微任务队列, 然后 JS 引擎会暂停当前协程的执行,将主线程的控制权转交给父协程执行,同时会将 promise_ 对象返回给父协程。
  4. 主线程的控制权已经交给父协程了,这时候父协程要做的一件事是调用 promise_.then 来监控 promise 状态的改变。
  5. 接下来继续执行父协程的流程,执行 console.log(3),并打印出来 3。
  6. 随后父协程将执行结束,在结束之前,会进入微任务的检查点,然后执行微任务队列,微任务队列中有 resolve(100) 的任务等待执行,执行到这里的时候,会触发 promise_.then 中的回调函数
    该回调函数被激活以后,会将主线程的控制权交给 foo 函数的协程,并同时将 value 值传给该协程。foo 协程激活之后,会把刚才的 value 值赋给了变量 a,然后 foo 协程继续执行后续语句

总结

  • 早期的异步回调函数虽然解决了同步阻塞的问题,但是容易写出回调地狱。
  • Generator 生成器最大的特点是可以控制函数的执行,也能勉强实现同步方式实现异步事件。
  • 使用 Promise 能很好地解决回调地狱的问题,但是这种方式充满了 Promise 的 then() 方法,如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程。后期移交维护的成本比较大。
  • async/await 可以算是异步编程的终极解决方案,它通过同步的方式写异步代码,可以把 await 看作是让出线程的标志,先去执行 async 函数外部的代码,等调用栈为空再回来调用 await 后面的代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值