详解用async/await来处理异步


Posted in Javascript onAugust 28, 2019

昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简洁,同时async/await 已经被标准化,是时候学习一下了。

先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。 写一个async 函数

async function timeout() {
return 'hello world';
}

语法很简单,就是在函数前面加上async关键字,来表示它是异步的,那怎么调用呢?async函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async函数调用之后加一句console.log;

async function timeout() {
  return 'hello world'
}
timeout();
console.log('虽然在后面,但是我先执行');

打开浏览器控制台,我们看到了

详解用async/await来处理异步

async函数 timeout 调用了,但是没有任何输出,它不是应该返回 'hello world', 先不要着急,看一看timeout()执行返回了什么?把上面的 timeout()语句改为console.log(timeout())

async function timeout() {
  return 'hello world'
}
console.log(timeout());
console.log('虽然在后面,但是我先执行');

继续看控制台

详解用async/await来处理异步

原来async函数返回的是一个promise对象,如果要获取到promise返回值,我们应该用then方法,继续修改代码

async function timeout() {
  return 'hello world'
}
timeout().then(result => {
  console.log(result);
})
console.log('虽然在后面,但是我先执行');

看控制台

详解用async/await来处理异步

我们获取到了"hello world', 同时timeout的执行也没有阻塞后面代码的执行,和我们刚才说的一致。

这时,你可能注意到控制台中的Promise有一个resolved,这是async函数内部的实现原理。如果async函数中有返回一个值 ,当调用该函数时,内部会调用Promise.solve()方法把它转化成一个promise对象作为返回,但如果timeout函数内部抛出错误呢? 那么就会调用Promise.reject()返回一个promise对象,这时修改一下timeout函数

async function timeout(flag) {
  if (flag) {
    return 'hello world'
  } else {
    throw 'my god, failure'
  }
}
console.log(timeout(true)) // 调用Promise.resolve() 返回promise 对象。
console.log(timeout(false)); // 调用Promise.reject() 返回promise 对象。

控制台如下:

详解用async/await来处理异步

如果函数内部抛出错误, promise对象有一个catch方法进行捕获。

timeout(false).catch(err => {
  console.log(err)
})

async关键字差不多了,我们再来考虑await关键字,await是等待的意思,那么它等待什么呢,它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多的是放一个返回promise对象的表达式。注意await关键字只能放到async函数里面

现在写一个函数,让它返回promise对象,该函数的作用是2s之后让数值乘以2

// 2s 之后返回双倍的值
function doubleAfter2seconds(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(2 * num)
    }, 2000);
  } )
}

现在再写一个async函数,从而可以使用await关键字, await后面放置的就是返回promise对象的一个表达式,所以它后面可以写上doubleAfter2seconds函数的调用

async function testResult() {
  let result = await doubleAfter2seconds(30);
  console.log(result);
}

现在调用testResult函数

testResult();

打开控制台,2s之后,输出了60.

现在我们看看代码的执行过程,调用testResult函数,它里面遇到了await, await表示等一下,代码就暂停到这里,不再向下执行了,它等什么呢?等后面的promise对象执行完毕,然后拿到promise resolve的值并进行返回,返回值拿到之后,它继续向下执行。具体到我们的代码,遇到await之后,代码就暂停执行了,等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30)返回的promise开始执行,2秒之后,promise resolve了,并返回了值为60,这时await才拿到返回值60,然后赋值给result,暂停结束,代码才开始继续执行,执行 console.log语句。

就这一个函数,我们可能看不出async/await的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

async function testResult() {
  let first = await doubleAfter2seconds(30);
  let second = await doubleAfter2seconds(50);
  let third = await doubleAfter2seconds(30);
  console.log(first + second + third);
}

6秒后,控制台输出220,我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。

再写一个真实的例子,我原来做过一个小功能,话费充值,当用户输入电话号码后,先查找这个电话号码所在的省和市,然后再根据省和市,找到可能充值的面值,进行展示。

为了模拟一下后端接口,我们新建一个node项目。 新建一个文件夹 async,然后npm init -y新建package.json文件,npm install express --save安装后端依赖,再新建server.js文件作为服务端代码, public文件夹作为静态文件的放置位置,在public文件夹里面放index.html文件,整个目录如下

详解用async/await来处理异步

server.js文件如下,建立最简单的web服务器

const express = require('express');
const app = express();// express.static 提供静态文件,就是html, css, js 文件
app.use(express.static('public'));

app.listen(3000, () => {
  console.log('server start');
})

再写index.html文件,我在这里用了vue构建页面,用axios发送ajax请求,为了简单,用cdn引入它们。 html部分很简单,一个输入框,让用户输入手机号,一个充值金额的展示区域, js部分,按照vue的要求搭建了模版

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Async/await</title>
  <!-- CDN 引入vue 和 axios -->
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
  <div id="app">

    <!-- 输入框区域 -->
    <div style="height:50px">
      <input type="text" placeholder="请输入电话号码" v-model="phoneNum">
      <button @click="getFaceResult">确定</button>
    </div>

    <!-- 充值面值 显示区域 -->
    <div>
      充值面值:
      <span v-for="item in faceList" :key='item'>
        {{item}}
      </span>
    </div>
  </div>

  <!-- js 代码区域 -->
  <script>
    new Vue({
      el: '#app',
      data: {
        phoneNum: '12345',
        faceList: ["20元", "30元", "50元"]
      },
      methods: {
        getFaceResult() {

        }
      }
    })
  </script>
</body>
</html>

为了得到用户输入的手机号,给input输入框添加v-model指令,绑定phoneNum变量。展示区域则是绑定到faceList数组,v-for指令进行展示,这时命令行nodemon server启动服务器,如果你没有安装nodemon,可以npm install -g nodemon安装它。启动成功后,在浏览器中输入 http://localhost:3000,可以看到页面如下,展示正确

详解用async/await来处理异步

现在我们来动态获取充值面值。当点击确定按钮时, 我们首先要根据手机号得到省和市,所以写一个方法来发送请求获取省和市,方法命名为getLocation, 接受一个参数phoneNum , 后台接口名为phoneLocation,当获取到城市位置以后,我们再发送请求获取充值面值,所以还要再写一个方法getFaceList, 它接受两个参数, province 和city, 后台接口为faceList,在methods 下面添加这两个方法getLocation, getFaceList

methods: {
      //获取到城市信息
      getLocation(phoneNum) {
        return axios.post('phoneLocation', {
          phoneNum
        })
      },
      // 获取面值
      getFaceList(province, city) {
        return axios.post('/faceList', {
          province,
          city
        })
      },
      // 点击确定按钮时,获取面值列表
      getFaceResult () {
        
      }
    }

现在再把两个后台接口写好,为了演示,写的非常简单,没有进行任何的验证,只是返回前端所需要的数据。Express 写这种简单的接口还是非常方便的,在app.use 和app.listen 之间添加如下代码

// 电话号码返回省和市,为了模拟延迟,使用了setTimeout
app.post('/phoneLocation', (req, res) => {
  setTimeout(() => {
    res.json({
      success: true,
      obj: {
        province: '广东',
        city: '深圳'
      }
    })
  }, 1000);
})

// 返回面值列表
app.post('/faceList', (req, res) => {
  setTimeout(() => {
    res.json(
      {
        success: true,
        obj:['20元', '30元', '50元']
      }
      
    )
  }, 1000);
})

最后是前端页面中的click 事件的getFaceResult, 由于axios 返回的是promise 对象,我们使用then 的链式写法,先调用getLocation方法,在其then方法中获取省和市,然后再在里面调用getFaceList,再在getFaceList的then方法获取面值列表,

// 点击确定按钮时,获取面值列表
      getFaceResult () {
        this.getLocation(this.phoneNum)
          .then(res => {
            if (res.status === 200 && res.data.success) {
              let province = res.data.obj.province;
              let city = res.data.obj.city;

              this.getFaceList(province, city)
                .then(res => {
                  if(res.status === 200 && res.data.success) {
                    this.faceList = res.data.obj
                  }
                })
            }
          })
          .catch(err => {
            console.log(err)
          })
      }

现在点击确定按钮,可以看到页面中输出了从后台返回的面值列表。这时你看到了then的链式写法,有一点回调地域的感觉。现在我们在有async/ await来改造一下。

首先把getFaceResult转化成一个async函数,就是在其前面加async,因为它的调用方法和普通函数的调用方法是一致,所以没有什么问题。然后就把getLocation和getFaceList放到await后面,等待执行, getFaceResult 函数修改如下

// 点击确定按钮时,获取面值列表
      async getFaceResult () {
        let location = await this.getLocation(this.phoneNum);
        if (location.data.success) {
          let province = location.data.obj.province;
          let city = location.data.obj.city;
          let result = await this.getFaceList(province, city);
          if (result.data.success) {
            this.faceList = result.data.obj;
          }
        }
      }

现在代码的书写方式,就像写同步代码一样,没有回调的感觉,非常舒服。

现在就还差一点需要说明,那就是怎么处理异常,如果请求发生异常,怎么处理?它用的是try/catch来捕获异常,把await放到 try中进行执行,如有异常,就使用catch进行处理。

async getFaceResult () {
        try {
          let location = await this.getLocation(this.phoneNum);
          if (location.data.success) {
            let province = location.data.obj.province;
            let city = location.data.obj.city;
            let result = await this.getFaceList(province, city);
            if (result.data.success) {
              this.faceList = result.data.obj;
            }
          }
        } catch(err) {
          console.log(err);
        }
      }

现在把服务器停掉,可以看到控制台中输出net Erorr,整个程序正常运行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript高级程序设计 阅读笔记(十二) js内置对象Math
Aug 14 Javascript
js模拟select下拉菜单控件的代码
May 08 Javascript
JS获取DropDownList的value值与text值的示例代码
Jan 07 Javascript
javascript从image转换为base64位编码的String
Jul 29 Javascript
jQuery实现根据滚动条位置加载相应内容功能
Jul 18 Javascript
微信小程序 Video API实例详解
Oct 02 Javascript
Javascript使用uploadify来实现多文件上传
Nov 16 Javascript
整理一下常见的IE错误
Nov 18 Javascript
原生JS实现导航下拉菜单效果
Nov 25 Javascript
js实现敏感词过滤算法及实现逻辑
Jul 24 Javascript
最简单的vue消息提示全局组件的方法
Jun 16 Javascript
javascript自定义日期比较函数用法示例
Jul 22 Javascript
vue中监听返回键问题
Aug 28 #Javascript
微信小程序 拍照或从相册选取图片上传代码实例
Aug 28 #Javascript
小程序中this.setData的使用和注意事项
Aug 28 #Javascript
微信小程序 可搜索的地址选择实现详解
Aug 28 #Javascript
vue实现标签云效果的方法详解
Aug 28 #Javascript
微信小程序通过js实现瀑布流布局详解
Aug 28 #Javascript
TypeScript类型声明书写详解
Aug 28 #Javascript
You might like
php线性表顺序存储实现代码(增删查改)
2012/02/16 PHP
PHP 类相关函数的使用详解
2013/05/10 PHP
强制PHP命令行脚本单进程运行的方法
2014/04/15 PHP
php不使用copy()函数复制文件的方法
2015/03/13 PHP
PHP递归遍历指定文件夹内的文件实现方法
2016/11/15 PHP
PHP chop()函数讲解
2019/02/11 PHP
JQuery 操作select标签实现代码
2010/05/14 Javascript
Javascript Memoizer浅析
2014/10/16 Javascript
JavaScript中使用document.write向页面输出内容实例
2014/10/16 Javascript
js仿支付宝填写支付密码效果实现多方框输入密码
2016/03/09 Javascript
AngularJs表单验证实例详解
2016/05/30 Javascript
详解Angular中$cacheFactory缓存的使用
2016/08/19 Javascript
值得分享的bootstrap table实例
2016/09/22 Javascript
原生JavaScript制作计算器
2016/10/16 Javascript
浅谈javascript中的数据类型转换
2016/12/27 Javascript
Angular2开发——组件规划篇
2017/03/28 Javascript
Angular2学习教程之TemplateRef和ViewContainerRef详解
2017/05/25 Javascript
javascript浏览器用户代理检测脚本实现方法
2017/10/27 Javascript
针对Vue路由history模式下Nginx后台配置操作
2020/10/22 Javascript
Python判断Abundant Number的方法
2015/06/15 Python
python ansible服务及剧本编写
2017/12/29 Python
python生成ppt的方法
2018/06/07 Python
Python命名空间的本质和加载顺序
2018/12/17 Python
python实现文本进度条 程序进度条 加载进度条 单行刷新功能
2019/07/03 Python
Python collections模块使用方法详解
2019/08/28 Python
python使用beautifulsoup4爬取酷狗音乐代码实例
2019/12/04 Python
html5 Canvas画图教程(11)—使用lineTo/arc/bezierCurveTo画椭圆形
2013/01/09 HTML / CSS
Java中的异常处理机制的简单原理和应用
2013/04/27 面试题
银行毕业实习自我鉴定
2013/09/19 职场文书
会计专业应届生求职信
2013/11/24 职场文书
活动总结报告格式
2014/05/09 职场文书
爱的奉献演讲稿
2014/09/10 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
大学生在校表现评语
2014/12/31 职场文书
2016年党员创先争优承诺书
2016/03/25 职场文书
公司晚会主持词
2019/04/17 职场文书