Promise的进化 —— async:await

600 阅读7分钟

前言

随着版本的更新,我们已经从回调地狱(Callback Hell)走到了Promise的天堂,现在,又有了async/await这位新秀。让我们一起看看它们如何改变异步编程的世界,并且让代码变得不再那么臃肿。

1. 异步

异步:指的是当前代码的执行不影响后面代码的执行。当程序运行到异步的代码时,会将该异步的代码作为任务放进任务队列,而不是推入主线程的调用栈。等主线程执行完之后,再去任务队列里执行对应的任务即可。

众所周知,Promise的出现是为了解决异步,在我们学习async/await之前先来回顾一下promise。由于js是单线程的所以一次只能干一件事,当出现需要“耗时”的代码时便会出现异步,下面我们来看一个例子:

function a() {
  setTimeout(function () {
    console.log('a');
  }, 1000)
}

function b() {
  console.log('b');
}

a()
b()

// 输出:
// b
// a

虽然我们先调用了a但是a还是在b后面输出,因为a中有个定时器并且是属于宏任务,所以就产生了异步。我们如果想要让a先执行这时候就得让a返回一个promise然后a执行完了之后让b在.then的回调函数中执行,下面我们来看代码:

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      console.log('a');
      resolve()
    }, 1000)
  })
  
}

function b() {
  console.log('b');
}

a().then(() => {
  b()
})

// 输出:
// a
// b

2. async / await

虽然在promise出现之后可以帮我们很好的解决异步问题,但是大家会不会觉得用它的.then一直链接下去也有点麻烦,如果想调换一下顺序跟解决回调地狱没啥区别。随着时间的推移,官方也是扛不住了推出了新的async / await用来解决异步接下来我们来了解一下这一对的使用。

tips: async / await必须成对出现

  1. async 添加在函数声明之前,相当于在函数中返回了一个 Promise 的实例对象
  2. await 后面接的是异步,但必须是一个 Promise 的实例对象,await 会将后面的代码的执行结果返回出来
  3. .then如果没有返回值会默认返回一个Promise实例对象,如果有自己的返回值则会用自己的返回值把Promise覆盖

我们在使用async / await时,只需要创建个函数用来存放想要执行的代码。然后在函数前加上async,然后在这个匿名函数中放入异步代码,在异步代码之前加上await。将想要在异步代码之后执行的代码放在这个前面加了await的异步代码的下一段。

接下来我们来就上面的例子用 async / await 来实现一下:

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      console.log('a');
      resolve()
    }, 1000)
  })
}

function b() {
  console.log('b');
}

(async function fn() {
  await a()
  b()
})()
image.png

当我们运行的时候就会发现a先被打印输出后再执行的b,这时我们就不需要再去使用.then()了,并且还返回了一个Promise对象。当我们有多个异步代码想要按顺序执行的时候,只需要让他们按照执行顺序在前面分别加上await即可,下面我们来看代码:

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      console.log('a');
      resolve()
    }, 1000)
  })
}

function b() {
  console.log('b');
}

function c() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('c');
      resolve()
    }, 2000)
  })
}

(async() => {
  await a()
  await c()
  b()
})()

// 输出:
// a
// c
// b

这时候有同学可能就会疑惑了,如果我想让c先执行的话那该如何呢。这时候我们只需要将await a()await c()的位置调换即可,下面我们来看看运行效果:

image.png

到这里呢 async / await 的大致用法你已经掌握了,这种操作我们的日常使用场景是非常常见的,就比如我们发送接口请求,然后拿接口请求渲染页面或者打印接口请求时,就可以用async / await来先拿到接口传过来的数据,然后再进行下一步操作。

tipsawait会将后面接的代码先执行,并且将后续代码挤入微任务队列

3. 面试官:将async / await代码翻译成Promise

1736584662223.png

翻译这个词想必大家都不陌生,咱学编程的高低都会一点小外语,有同学会问这不都是英语吗还咋翻译呢?这里的翻译并不是广义上的翻译,这里的翻译呢我们可以理解为用Promise来实现 async/await的效果,接下来我们来看一段async/await的代码:

function getJson() {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log(2);
      resolve(2)
    }, 2000)
  })
}


async function testAsync() {
  let res = await getJson()
  console.log(3, res);
}

// 输出:
// 2
// 3 2

在我们将这段代码翻译之前,我们先来了解一下Promise的一点小知识,首先我们知道.then()只能在成功状态的Promise后执行,那我们如何返回一个成功状态的Promise呢,我们来浏览器看一个方法Promsie.resolve()

image.png

Promise.resolve() 会得到一个状态变更为成功的 promise 对象(相当于写new Promise(resolve())

我们可以看到当我们使用这个方法的时候成功返回了一个成功状态的Promise对象,这样的话我们就可以用它来直接实现异步。根据题目要求,我们只需要把同步代码放在异步代码后面的.then()中,并且得注意异步代码执行完了之后要返回一个Promise对象后面才能接.then()。有了这一思路接下来我们来如何翻译testAsync()

function getJson() {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log(2);
      resolve(2)
    }, 2000)
  })
}

function testAsync() {
  // 返回一个成功状态的Promise然后才能then
  return Promise.resolve().then(() => {
    return getJson()
  }).then((res) => {
    console.log(3, res);
  })
}
testAsync()
image.png

在上面的代码中我们根据async/await会返回一个Promise对象的特点,在testAsync()中直接返回了一个成功状态的Promise,然后根据await的特性让getJson()在.then()中先执行,由于getJson()执行完后会返回一个Promise,所以我们可以接着.then(),并且在这个.then()中放入我们想要执行的同步代码即可。

4. 红绿灯问题

我们在日常行驶在马路上的时候总是会碰到一些红绿灯,现在我们假设这些红绿灯的执行顺序都是红 --> 绿 --> 黄循环往复,现在我们先给出四个函数分别是red()、green()、yellow()、light(timer, cb):

function red() {
  console.log('红');
}
function green() {
  console.log('绿');
}
function yellow() {
  console.log('黄');
}

function light(timer, cb) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      cb()
      resolve()
    }, timer)
  })
}

在实现这个效果的时候我们注意到这几个函数是不会中断进行的,因为我们路口的红绿灯也没见它断过,所以我们可以用一个函数来包含这三个灯光函数,然后执行完了这几个函数之后再调用一下自身,这样就可以实现循环往复的效果,下面来看使用async/await实现:

async function step() {
  await light(3000, red)
  await light(1000, green)
  await light(2000, yellow)
  step()
}
step()
PixPin_2025-01-12_08-48-18.gif

根据浏览器的打印结果我们可以看到已经实现了这个效果,接下来我们来对它用Promise翻译一下来看看大家是否对async/await的实现有了一个初步的认识(我们只对async函数进行修改,在这里就省去返回Promise的步骤):

function step() {
  Promise.resolve().then(() => {
    return light(3000, red)
  }).then(() => {
    return light(1000, green)
  }).then(() => {
    return light(2000, yellow)
  }).then(() => {
    step()
  })
}
step()
PixPin_2025-01-12_08-53-03.gif

async/await在我们开发的时候通常用来解决异步问题,比如接口请求这类问题我们就可以使用它。最后谢谢各位大佬们的观看,希望本篇文章能让大家对async/await有一个简单的认识。

1732338928918.jpg