深入理解JavaScript的async/await

async 和 await 在干什么

任意一个名称都是有意义的,先从字面意思来理解。async 是“异步”的简写,而 await 的意思是等待。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 等待某个操作完成。

那么async/await到底是干嘛的呢?我们先来简单介绍一下。

  • async/await 是一种编写异步代码的新方法。之前异步代码的方案是回调和 promise。
  • async/await 是建立在 promise 的基础上。(对promise不熟悉的同学可以看一下这篇文章入门Promise的正确姿势)
  • async/await 像 promise 一样,也是非阻塞的。
  • async/await 让异步代码看起来、表现起来更像同步代码。这正是其威力所在。

async 起什么作用

这个问题的关键在于,async 函数是怎么处理它的返回值的!

我们当然希望它能直接通过 return 语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了。所以,写段代码来试试,看它到底会返回什么:

<script>
  async function test(){
    return 'hello async';
  }
  let result = test();
  console.log(result);
</script>

看到输出就恍然大悟了——输出的是一个 Promise 对象。

Promise {<resolved>: "hello async"}

所以,async 函数返回的是一个 Promise 对象。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样

async function test(){
   return 'hello async';
 }
 test().then((val) => {
   console.log(val);  //hello async
 })

现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。

联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

那么下一个关键点就在于 await 关键字了。

await 到底在等啥

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行。

function getSomething(){
  return "something";
}
async function testAsync(){
  return Promise.resolve('hello async');
}
async function test(){
  let v1 = await getSomething();
  let v2 = await testAsync();
  console.log(v1,v2);
}
test();
console.log('我执行了');

//执行结果为:
//我执行了
//something,hello async

await 等到了要等的,然后呢

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞(也就是第13行代码不会被阻塞),它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

async/await 帮我们干了啥

作个简单的比较

上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写。

function takeLongTime(){
  return new Promise((resolve) => {
    setTimeout(() => resolve('long time value'),1000);
  })
}
takeLongTime().then((v) => {
  console.log('get:',v);
})

如果改用 async/await 呢,会是这样。

function takeLongTime(){
  return new Promise((resolve) => {
    setTimeout(() => resolve('long time value'),1000);
  })
}
async function test(){
  let v = await takeLongTime();//等待异步操作的结果,阻塞后面代码的执行
  console.log(v);
}

眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。

又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?

async/await 的优势在于处理 then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

/*
 * 传入参数n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n+200,这个值将用于下一步骤
*/
function takeLongTime(n){
  return new Promise((resolve) => {
    setTimeout(() => resolve(n + 200),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);
}

现在用 Promise 方式来实现这三个步骤的处理。

function doIt(){
  console.time('doIt');
  let time1 = 300;
  step1(time1)
    .then((time2) => step2(time2))
    .then((time3) => step3(time3))  
    .then((result) => {
      console.log(`result is ${result}`);
      console.timeEnd("doIt");
    })
}

doIt();

//执行结果为:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1510.2490234375ms

输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

如果用 async/await 来实现呢,会是这样。

async function doIt() {
  console.time('doIt');
  let time1 = 300;
  let time2 = await step1(time1);//将Promise对象resolve(n+200)的值赋给time2
  let time3 = await step1(time2);
  let result = await step1(time3);
  console.log(`result is ${result}`);
  console.timeEnd('doIt');
}

doIt();

//执行结果为:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1512.904296875ms

结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样。

还有更酷的

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

/*
 * 传入参数n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n+200,这个值将用于下一步骤
*/
function takeLongTime(n){
  return new Promise((resolve) => {
    setTimeout(() => resolve(n + 200),n);
  })
}
function step1(n){
  console.log(`step1 with ${n}`);
  return takeLongTime(n);
}
function step2(m,n){
  console.log(`step2 with ${m} + ${n}`);
  return takeLongTime(m + n);
}
function step3(k,m,n){
  console.log(`step3 with ${k} + ${m} + ${n}`);
  return takeLongTime(k + m + n);
}

这回先用 async/await 来写:

async function doIt() {
  console.time('doIt');
  let time1 = 300;
  let time2 = await step1(time1);//将Promise对象resolve(n+200)的值赋给time2
  let time3 = await step2(time2,time1);
  let result = await step3(time3,time2,time1);
  console.log(`result is ${result}`);
  console.timeEnd('doIt');
}

doIt();

//执行结果为:
//step1 with 300
//step2 with 500 + 300
//step3 with 1000 + 500 + 300
//result is 2000
//doIt: 2916.655029296875ms

除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?

function doIt() {
  console.time('doIt');
  let time1 = 300;
  step1(time1)
    .then((time2) => {
      return step2(time1,time2)
          .then((time3) => [time1,time2,time3])//step3需要用到time1,time2,time3,因此需要返回
    })
    .then((times) => {
      let [time1,time2,time3] = times;
      return step3(time1,time2,time3)
    })
    .then((result) => {
      console.log(`result is ${result}`);
      console.timeEnd('doIt');
    })
}

doIt();

//执行结果为:
//step1 with 300
//step2 with 300 + 500
//step3 with 300 + 500 + 1000
//result is 2000
//doIt: 2919.49609375ms

有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕!

注意点
就目前来说,已经理解 async/await 了吧?但其实还有一些事情没提及——Promise 有可能 reject 啊,怎么处理呢?

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

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

//另一种写法
async function myFunction() {
  await somethingThatReturnAPromise().catch(function(err) {
    console.log(err);
  })
}
(0)

相关推荐

  • 详解Node.js中的Async和Await函数

    在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise. 异步语言结构在其他语言中已经存在了,像c#的async/await.Kotlin的coroutines.go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了. Node中的async函数是什么? 当函数声明为一个Async函数它会返回一个 AsyncFunction 对象,它们类似于 Generator 因为执可以被暂停.唯

  • JavaScript中的await/async的作用和用法

    await/async 是 ES7 最重要特性之一,它是目前为止 JS 最佳的异步解决方案了.虽然没有在 ES2016 中录入,但很快就到来,目前已经在 ES-Next Stage 4 阶段. 直接上例子,比如我们需要按顺序获取:产品数据=>用户数据=>评论数据 老朋友 Ajax 传统的写法,无需解释 // 获取产品数据 ajax('products.json', (products) => { console.log('AJAX/products >>>', JSON

  • Js中async/await的执行顺序详解

    前言 虽然大家知道async/await,但是很多人对这个方法中内部怎么执行的还不是很了解,本文是我看了一遍技术博客理解 JavaScript 的 async/await(如果对async/await不熟悉可以先看下这篇文章)后拓展了一下,我理了一下await之后js的执行顺序,希望可以给别人解疑答惑,先简单介绍一下async/await. async/await 是一种编写异步代码的新方法.之前异步代码的方案是回调和 promise. async/await 是建立在 promise 的基础上

  • NodeJs通过async/await处理异步的方法

    场景 远古时代 我们在编写express后台,经常要有许多异步IO的处理.在远古时代,我们都是用chunk函数处理,也就是我们最熟悉的那种默认第一个参数是error的函数.我们来模拟一个Mongo数据库的操作,感受一下. mongoDb.open(function(err, db){ if(!err){ db.collection("users", function(err, collection){ if(!err){ let person = {name: "yika&q

  • async/await与promise(nodejs中的异步操作问题)

    举例写文章详情页面的时候的一个场景:首先更改文章详情中的 PV,然后读取文章详情,然后根据文章详情中文章 Id 查阅该文章评论和该文章作者信息.获取全部数据之后渲染文章详情页.数据库操作都是异步的,最直接想到的办法就是一层一层的回调函数,问题出来了:十分不雅观,要是层再多一点还会有更多麻烦.怎么解决?业内为了处理异步操作问题也是拼了,什么async,q,bluebird,co,处理方式不同,各有千秋,感兴趣可以了解一下,但是惊喜的发现nodejs 7.6已经默认支持ES7中的 async/awa

  • 深入理解JavaScript的async/await

    async 和 await 在干什么 任意一个名称都是有意义的,先从字面意思来理解.async 是"异步"的简写,而 await 的意思是等待.所以应该很好理解 async 用于申明一个 function 是异步的,而 await 等待某个操作完成. 那么async/await到底是干嘛的呢?我们先来简单介绍一下. async/await 是一种编写异步代码的新方法.之前异步代码的方案是回调和 promise. async/await 是建立在 promise 的基础上.(对promi

  • JavaScript中async,await的使用和方法

    JS中 async函数和await 关键字 function hellworld() { return "您好!美好世界!"; } console.log(hellworld()); // 您好!美好世界! async function asyHellworld() { return "您好!美好世界!"; } console.log(asyHellworld()); // Promise { '您好!美好世界!' } 普通函数 hellworld 将简单地返回字符

  • JavaScript中async await更优雅的错误处理方式

    目录 背景 为什么要错误处理 async await 更优雅的错误处理 小结 总结 背景 团队来了新的小伙伴,发现我们的团队代码规范中,要给 async  await 添加 try...catch.他感觉很疑惑,假如有很多个(不集中),那不是要加很多个地方?那不是很不优雅? 为什么要错误处理 JavaScript 是一个单线程的语言,假如不加 try ...catch ,会导致直接报错无法继续执行.当然不意味着你代码中一定要用 try...catch 包住,使用 try...catch 意味着你

  • 深入理解ES7的async/await的用法

    在最开始学习ES6的Promise时,曾写过一篇博文 <promise和co搭配生成器函数方式解决js代码异步流程的比较> ,文章中对比了使用Promise和co模块搭配生成器函数解决js异步的异同. 在文章末尾,提到了ES7的async和await,只是当时只是简单的提了一下,并未做深入探讨. 在前两个月发布的Nodejs V7中,已添加了对async和await的支持,今天就来对这个东东做一下深入的探究.以更加优雅的方法写异步代码. async/await是什么 async/await可以

  • 详解JavaScript Promise和Async/Await

    概述 一般在开发中,查询网络API操作时往往是比较耗时的,这意味着可能需要一段时间的等待才能获得响应.因此,为了避免程序在请求时无响应的情况,异步编程就成为了开发人员的一项基本技能. 在JavaScript中处理异步操作时,通常我们经常会听到 "Promise "这个概念.但要理解它的工作原理及使用方法可能会比较抽象和难以理解. 四个示例 那么,在本文中我们将会通过实践的方式让你能更快速的理解它们的概念和用法,所以与许多传统干巴巴的教程都不同,我们将通过以下四个示例开始: 示例1:用生

  • Async/Await替代Promise的6个理由

    前言 Node.js 7.6 已经支持 async/await 了,如果你还没有试过,这篇博客将告诉你为什么要用它. Async/Await 简介 对于从未听说过 async/await 的朋友,下面是简介: async/await 是写异步代码的新方式,以前的方法有回调函数和Promise. async/await 是基于 Promise 实现的,它不能用于普通的回调函数. async/await 与 Promise 一样,是非阻塞的. async/await 使得异步代码看起来像同步代码,这

  • JavaScript引擎实现async/await的方法实例

    目录 前言 生成器 VS 协程 async/await async await 小结 总结 前言 我们都知道Promise 能很好地解决回调地狱的问题,但是这种方式充满了 Promise 的 then() 方法,如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程,使用 promise.then 也是相当复杂,虽然整个请求流程已经线性化了,但是代码里面包含了大量的 then 函数,使得代码依然不是太容易阅读.基于这个原因,ES7 引入了 async/

  • JavaScript详解使用Promise处理回调地狱与async await修饰符

    目录 Promise 回调地狱 Promise简介 Promise简单使用 async和await 修饰符 小结 Promise Promise能够处理异步程序. 回调地狱 JS中或node中,都大量的使用了回调函数进行异步操作,而异步操作什么时候返回结果是不可控的,如果我们希望几个异步请求按照顺序来执行,那么就需要将这些异步操作嵌套起来,嵌套的层数特别多,就会形成回调地狱 或者叫做 横向金字塔. 案例:有a.txt.b.txt.c.txt三个文件,使用fs模板按照顺序来读取里面的内容,代码:

  • JavaScript async/await使用详解

    目录 一.简介 二.async 三.await 四.案例 附-直接量/字面量 一.简介 async/await是ES20717引入的,主要是简化Promise调用操作,实现了以异步操作像同步的方式去执行,async外部是异步执行的,同步是await的作用. 二.async async,英文意思是异步,当函数(包括函数语句.函数表达式.Lambda表达式)前有async关键字的时候,并且该函数有返回值,函数执行成功,那么该函数就会调用Promise.resove()并隐式的返回一个Promise对

随机推荐