前端异步中的async/await

在前端开发中,经常会遇到异步的情况。异步操作一直是JS中不可或缺的一环。从最开始使用回调函数实现异步操作,到后面的Promise,再到ES2017引入的async函数,异步操作逐渐进化,变得越来越简单方便,接下来就仔细看看在ES2017引入了async函数后,异步操作产生了哪些变化。

通常情况下,使用异步函数,都是async/await一起用的,但下面分别讲解async和await在异步函数中到底有什么用。

1、单独使用async:

通常情况下使用async命令是因为函数内部有await命令,因为await命令只能出现在async函数里面,否则会报语法错误,这就是为什么async/await成对出现的原因,但是如果对一个普通函数单独加个async会是什么结果呢?来看个例子:

async function test () {
  let a = 2
  return a
}
const res = test()
console.log(res)
使用async的函数返回的结果.png

由例子可以看成,async函数返回的是一个Promise对象,如果函数中有返回值,async会把这个返回值通过Promise.resolve()封装成Promise对象,要取这个值也很简单,直接通过then()就能取出,代码如下:

res.then(a => {
  console.log(a) // 2
})

2、单独使用await:

一般情况下,await命令后面接的是一个Promise对象,等待Promise对象状态发生变化,得到返回值,但是也可以接任意表达式的返回结果,来看个例子:

function a () {
  return 'a'
}
async function b () {
  return 'b'
}
const c = await a()
const d = await b()
console.log(c, d) // 'a' 'b'

由例子可以看到await后面不管接的是什么表达式,都能等待到结果的返回,当等到的不是Promise对象时,就将等到的结果返回,当等到的结果是一个Promise对象时,会阻塞后面的代码,等待Promise对象状态变化,得到对应的值作为await等待的结果,这里的阻塞指的是async内部的阻塞,async函数的调用并不会阻塞。

3、解决了什么问题:

Promise...then语法已经解决了以前一直存在的多层回调嵌套的问题,那问什么还要用async/await呢?要解答这个问题先来看一段Promise代码:

function login () {
  return new Promise(resolve => {
    resolve('aaaa')
  })
}
function getUserInfo (token) {
  return new Promise(resolve => {
    if (token) {
      resolve({
        isVip: true
      })
    }
  })
}
function getVipGoods (userInfo) {
  return new Promise(resolve => {
    if (userInfo.isVip) {
      resolve({
        id: 'xxx',
        price: 'xxx'
      })
    }
  })
}
function showVipGoods (vipGoods) {
  console.log(vipGoods.id + '----' + vipGoods.price)
}
login()
  .then(token => getUserInfo(token))
  .then(userInfo => getVipGoods(userInfo))
  .then(vipGoods => showVipGoods(vipGoods))

如例子所示,每一个Promise相当于一个异步的网络请求,通常一个业务流程需要多个网络请求,而且网络请求网络请求都依赖一个的请求结果,上例就是Promise模拟了这个过程,下面我们再来看看用async/await会有什么不同,如例:

async function call() {
  const token = await login()
  const userInfo = await getUserInfo(token)
  const vipGoods = await getVipGoods(userInfo)
  showVipGoods(vipGoods)
}
call()

和Promise的then链调用相比,async/await的调用更加清晰简单,和同步代码一样。

4、带来了什么问题:

使用async/await我们经常会忽略一个问题,同步执行带来的时间累加,会导致程序变慢,有时候我们的代码可以写成并发执行,但是由于async/await做成了继发执行,来看一个例子:

function test () {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('test')
      resolve()
    }, 1000)
  })
}
function test1 () {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('test1')
      resolve()
    }, 1000)
  })
}
function test2 () {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('test2')
      resolve()
    }, 1000)
  })
}
async function call () {
  await test()
  await test1()
  await test2()
}
call ()

上面代码继发执行,所花时间是:

实际上,这段代码执行顺序,我并不关心,继发执行就浪费大量执行时间,下面改成并发执行:

function call () {
  Promise.all([test(), test1(), test2()])
}
call()

所花时间:
因此在使用async/await时需要特别注意这一点。

4、循环中的小问题:

在写JS循环时,JS提供了许多好用数组api接口,forEach就是其中一个,但是碰上了async/await,可能就悲剧了,得到了不是你想要的结果,来看一个例子:

function getUserInfo (id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: id,
        name: 'xxx',
        age: 'xxx'
      })
    }, 200)
  })
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
users.forEach(async user => {
  let info = await getUserInfo(user.id)
  userInfos.push(info)
})
console.log(userInfos) // []

上面这段代码是不是很熟悉,模拟获取多个用户的用户信息,然后得到一个用户信息数组,但是很遗憾,上面的userInfos得到的是一个空数组,上面这段代码加上了async/await后,forEach循环就变成了异步的,因此不会等到所有用户信息都请求完才打印userInfos,想要等待结果的返回再打印,还是要回到老式的for循环,来看代码:

function getUserInfo (id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: id,
        name: 'xxx',
        age: 'xxx'
      })
    }, 200)
  })
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
async function call() {
  for (user of users) {
    let info = await getUserInfo(user.id)
    userInfos.push(info)
  }
  console.log(userInfos)
}
call()

上面这种写法是继发式的,也就是会等前面一个任务执行完,再执行下一个,但是也许你并不关心执行过程,只要拿到想要的结果就行了,这时并发式的效率会更高,来看代码:

function getUserInfo (id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: id,
        name: 'xxx',
        age: 'xxx'
      })
    }, 200)
  })
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
const promises = users.map(user => getUserInfo(user.id))
Promise.all(promises).then(res => {
  userInfos = res
  console.log(userInfos)
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容