乐趣区

关于async-await:asyncawait-使用方式

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

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

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

async function timeout() {return 'hello world'}
timeout();
console.log('尽管在前面,然而我先执行');

关上浏览器控制台,咱们看到了

async 函数 timeout 调用了,然而没有任何输入,它不是应该返回 ‘hello world’, 先不要焦急,看一看 timeout() 执行返回了什么?把下面的 timeout() 语句改为 console.log(timeout())

async function timeout() {return 'hello world'}
console.log(timeout());
console.log('尽管在前面,然而我先执行');

持续看控制台

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

async function timeout() {return 'hello world'}
timeout().then(result => {console.log(result);
})
console.log('尽管在前面,然而我先执行');

看控制台

咱们获取到了 ”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 对象。

控制台如下:

如果函数外部抛出谬误,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 文件,整个目录如下

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, 能够看到页面如下,展现正确

当初咱们来动静获取充值面值。当点击确定按钮时,咱们首先要依据手机号失去省和市,所以写一个办法来发送申请获取省和市,办法命名为 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,整个程序失常运行。

退出移动版