Js异步编程async/await 函数

更新日期: 2019-08-18阅读: 2.1k标签: 异步
提起异步编程,大家可能会想到事件监听、回调函数、发布订阅、Promise 对象、Generator 函数、async 函数等,本篇主要讲解的是 async 函数,很多人认为它是异步编程的终极解决方案。


一、async 函数是什么?

摘自阮老师的文章:一句话,它就是 Generator 函数的语法糖;也有人说它是 Promise 的语法糖。

如果你对 Promise 对象、 Generator 函数不是特别了解的话,建议先看一下阮老师 ECMAScript6 入门中的关于 Promise 对象 和 Generator 函数的介绍。


二、async

1.async 声明的函数的返回本质上是一个 promise 对象(很重要。。。)

就是说只要你声明了这个函数是 async,那么内部不管你怎么处理,它的返回肯定是个 Promise。

async function myAsync () {
   return 'hello world'
}
let result = myAsync()
console.log(result)

2.async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数

myAsync().then((val) => {
   console.log(val)
})

3.async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。

也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数。

function getNum () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(1000)
    }, 1000)
  })
}

async function myAsync () {
  let num = await getNum()
  return num + 1000
}

myAsync().then((val) => {
  console.log(val)
})

上面代码中,函数 myAsync 内部有两个操作:获取 num,加 1000 后并返回结果。只有这两个操作全部完成,才会执行 then 方法里面的 console.log(val)。


三、await

1.正常情况下,await 命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

2.await 的意思就是让 JavaScript 引擎等待,直到 await 命令后代码执行完毕,然后继续执行 await 命令后面的代码。

3.这个行为不会耗费 CPU 资源,因为引擎可以同时处理其他任务:执行其他脚本,处理事件等。

我们来看个例子,可以试着写出执行结果。

function myAwait () {
   return new Promise((resolve) => {
     resolve('hello world!')
   })
}

async function myAsync(){
   console.log('async begin')
   let wait = await myAwait()
   console.log(wait)
   console.log('async end')
   return wait
}

console.log('begin')
let result = myAsync()
console.log(result)
console.log('end')

以上结果是在谷歌浏览器下执行的结果,“async end” 在 “hello world!” 之后输出,因为 await 阻塞了 “async end” 的输出,但是 ‘end’ 在 “hello world!”之前输出了,因为 await 只会阻塞 async 函数中 await 后面代码的执行,不会阻塞其他代码的执行。


四、Promise 、Generator、async 异步编程示例

了解了 async 和 await 后,我们一起来看一个完整的例子。

假如我们做完一件事,需要分三个步骤,每一个步骤都需要上一步的执行结果,我们分别看一下 Promise 、 Generator 和 async 都是怎么实现的。

/* 花费时间 */
function takeLongTime (n) {
  return new Promise(resolve => {
    setTimeout(() => resolve(n + 1000), n)
  })
}

/* 步骤一 */
function step1 (n) {
  console.log(`step1 with ${n}`)
  return takeLongTime(n)
}

/* 步骤二 */
function step2 (n) {
  console.log(`step2 with ${n}`)
  return takeLongTime(n)
}

/* 步骤三 */
function step3 (n) {
  console.log(`step3 with ${n}`)
  return takeLongTime(n)
}

1. Promise 的实现:

function doIt () {
  let time1 = 1000
  step1(time1)
      .then(time2 => step2(time2))
      .then(time3 => step3(time3))
      .then(result => {
        console.log(`result is ${result}`)
      })
}

doIt()

2. Generator 的实现:

/** 执行器
  * Generator 函数不能自动执行,我们需要借助执行器
*/
function run (generator) {
  let iterator = generator()
  let result = iterator.next()
  function step () {
    if(!result.done) {
      let promise = Promise.resolve(result.value)
      promise.then((value) => {
        result = iterator.next(value)
        step()
      }).catch((error) => {
        result = iterator.throw(error)
        step()
      })
    }
  }
  step()
}

function *doIt () {
  let time1 = 1000
  let time2 = yield step1(time1)
  let time3 = yield step2(time2)
  let result = yield step3(time3)
  console.log(`result is ${result}`)
}

run(doIt)

3. async 的实现:

async function doIt () {
  let time1 = 1000
  let time2 = await step1(time1)
  let time3 = await step2(time2)
  let result = await step3(time3)
  console.log(`result is ${result}`)
}

doIt()

三种方法执行结果都如下:

对比以上三种实现方式:

1.由于 Promise 的 then 方法返回的是一个新的 Promise,所以 Promise 可以通过链式调用实现异步编程。

2.async 函数和 Generator 函数就比较有意思了,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,并内置执行器,仅此而已。

3.不难发现,async 的写法更具语义化,并且更加清晰。


五、使用注意事项

1.await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await命令放在 try...catch 代码块中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

2.多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

function getA () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('A')
    }, 1000)
  })
}

function getB () {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('B')
    }, 1000)
  })
}

async function myAsync () {
  let A = await getA();
  console.log('A: ', A)
  let B = await getB();
  console.log('B: ', B)
}

myAsync()

上面代码中,getA 和 getB 是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有 getA 完成以后,才会执行 getB,完全可以让它们同时触发。

// 写法一
async function myAsync () {
  let [A, B] = await Promise.all([getA(), getB()])
  console.log('A: ', A)
  console.log('B: ', B)
}

myAsync()
// 写法二
async function myAsync () {
  let aPromise = getA()
  let bPromise = getB()
  let A = await aPromise
  let B = await bPromise
  console.log('A: ', A)
  console.log('B: ', B)
}

myAsync()

上面两种写法,getA 和 getB 都是同时触发,这样就会缩短程序的执行时间。

3.await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。


六、小结

函数前面的关键字 async 有两个作用:

1.让这个函数返回一个 promise
2.允许在函数内部使用 await,这个 await 关键字又让 JavaScript 引擎等待直到 promise 完成,如果有错误,就会抛出异常,否则,就返回结果。

这两个关键字一起用就提供了一个通俗易懂的方式来控制异步编程,并且易于读写。


七、附加题:async、promise、setTimeout 的执行顺序

相信你对 Promise、Generator、async 已经有了一定的了解了,若加上 setTimeout,你对代码的执行顺序还很清晰吗?

我们来看一道写出执行结果的题,相信很多同学面试的时候都遇到过,是不是很懵逼!!!

async function async1() {
   console.log('async1 start')
   await async2()
   console.log('async1 end')
}

async function async2() {
   console.log('async2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
},0)

async1()

new Promise((resolve) => {
    console.log('promise1')
    resolve()
}).then(() => {
    console.log('promise2')
})

console.log('script end')

执行结果(不同浏览器执行结果可能不同,下面结果用的谷歌):


请大家谨记执行规则:setTimeout 的优先级最低,没有 async 和 promise 级别高(其实 async 和 promise 是一样的,因为调用 async 方法时就是返回一个 promise 对象),async 和 promise 的 .then 就看谁先进入到的任务队列里面,任务队列里面有先进先出的概念。

按照这个规则,相信你很快就能写出执行结果了。


链接: https://fly63.com/article/detial/5370

通过alert方法,去理解js中阻塞、局部作用域、同步/异步任务

javascript中alert是Bom中的成员函数,alert对话框是模态的,具有阻塞性质的,不点击是不会执行后续代码的。js的阻塞是指在调用结果返回之前,当前线程会被挂起, 只有在得到结果之后才会继续执行。

如何优化async代码?更好的编写async异步函数

如何优化async代码?更好的编写async函数:使用return Promise.reject()在async函数中抛出异常,让相互之间没有依赖关系的异步函数同时执行,不要在循环的回调中/for、while循环中使用await,用map来代替它

【JS】异步处理机制的几种方式

Javascript语言的执行环境是单线程,异步模式非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。

js异步加载方式有哪些?_详解异步加载js的多种方案

js异步加载又被称为非阻塞加载,浏览器在下载JS的同时,还会进行后续页面处理。那么如何实现js异步加载呢?下面整理了多种实现方案供大家参考。异步加载js方案:Script Dom Element、onload时的异步加载、$(document).ready()、async属性、defer属性、es6模块type=module属性

Nodejs 处理异步(获取异步数据并处理)的方法

回调函数方式:将异步方法如readFile封装到一个自定义函数中,通过将异步方法得到的结果传给自定义方法的回调函数参数。事件驱动方式:使用node events模块,利用其EventEmitter对象

JS常用的几种异步流程控制

JavaScript引擎是基于单线程 (Single-threaded) 事件循环的概念构建的,同一时刻只允许一个代码块在执行,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列 (job queue) 中

前端异步编程之Promise和async的用法

传统的异步解决方案采用回调函数和事件监听的方式,而这里主要记录两种异步编程的新方案:ES6的新语法Promise;ES2017引入的async函数;Generator函数(略)

异步的JavaScript

JS本身是一门单线程的语言,所以在执行一些需要等待的任务(eg.等待服务器响应,等待用户输入等)时就会阻塞其他代码。如果在浏览器中JS线程阻塞了,浏览器可能会失去响应,从而造成不好的用户体验。

js 多个异步的并发控制

请实现如下的函数,可以批量请求数据,所有的URL地址在urls参数中,同时可以通过max参数 控制请求的并发度。当所有的请求结束后,需要执行callback回调。发请求的函数可以直接使用fetch。

解读react的setSate的异步问题

将setState()认为是一次请求而不是一次立即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState之后,能够立刻拿到改变的结果。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!