用async/await 来处理异步
近期项目中大量使用async,从服务器获取数据,解决一些并发传参问题。代码很简单。在此也看了一些博客,现在async/await已经大范围让使用,是时候总结一波啦。
先说一下async的语法,它作为一个关键字放在函数前面,用于表示函数是一个异步函数,因为async就是异步的异步,异步函数也就是意味着这个函数的执行不会阻塞后面代码的执行。简单的写一个async函数
async function hello(){
return 'hello world';
}
语法很简单,就是在函数前面加上async,他就成异步函数啦。怎么去调用呢,其实一样,平时怎么使用函数我们就怎么去调用它,直接加括号就可以啦,为了表示它没有阻塞后面的代码我们写个案例
async function hello(){
return 'hello world';
}
console.log(hello());
console.log('我是先执行的');
好像没有什么用,别急,首先我们看到hello()返回的是一个promise对象,其次它好像没有去异步执行。
async异步函数返回的是一个promise对象,如果要获取到promise返回值,我们就应该使用.then方法。
async function hello(){
return 'hello world';
}
hello().then(result=>{
console.log(result);
})
console.log('我是先执行的');
然后就没问题啦,一个简单的异步函数就OK啦,hello执行的时候没有阻塞后面代码的执行,和我们之前说的一样。
你可能注意到控制台中的promise有一个resolved,这是async函数内部的实现原理,如果async函数中返回一个值,当调用该函数时,内部会调用promise.solve()方法把它转化成一个promise()对象作为返回,但如果hello函数内部发生错误呢?那么就会调用promise.reject()返回一个promise对象,这时修改一下hello()函数
async function hello(flag){
if(flag){
return 'hello world';
}else{
throw 'happen Error';
}
}
console.log(hello(0));
console.log(hello(1));
如果函数内部发生错误,promise对象有一个catch方法进行捕获。
hello(0).catch(err=>{
console.log(err);
})
async是差不多啦,我们再来熟悉一下await关键字,await是等待的意思,那么它在等待什么呢,它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多放的是一个promise对象的表达式。注意await关键字,只能放在async函数里面!!!
function awaitMethod(num){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(2*num);
},2000)
})
}
我们再写一个async函数,从而可以使用await关键字,await后面放置的是返回promise对象的一个表达式,所以它后面可以写上awaitMethod函数的调用。
async function test (){
let result = await awaitMethod(30);
console.log(result);
}
然后我们调用这个函数
test();
2秒钟之后控制台输出60。
现在我们看看代码的执行过程,调用test函数时,它遇到了里面的await关键字,await代表等待,代码到这里会暂停,它在等什么呢,等待后面的promise对象执行完成,然后拿到promise的返回值,拿到返回值之后它继续往下执行,直到console.log()执行。
就这一个函数,或许我们看不出来asycn和await的作用,如果我们要计算3个数的值,然后把他们的值加起来输出,或许就看的明显啦。
async function test(){
let result = await awaitMethod(30);
let result1 = await awaitMethod(50);
let result2 = await awaitMethod(30);
console.log(result+result1+result2 );
}
6秒之后控制台,输出220,我们可以看到,写异步代码的就像写同步代码一样啦,再也没有什么回调地狱这一说啦。
下来,我们使用node+vue写一个简单的实例,什么实例呢,这个实例需要用户先拿到省和市,然后在根据省和市找到充值的面值,进行展示。
首先我们要模拟一下后台接口,我们新建一个node项目,新建一个文件夹AsyncAndAwait,然后npm init -y新建package.json文件,npm install express --save安装一下express的依赖,然后在新建一个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');
})
接下来写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指令进行展示,这是命令行node server启动服务器,启动成功之后,在浏览器输入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,因为它的调用方法和普通函数的调用方法是一致的,所以没有什么问题。然后就把 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);
}
}
OK啦,这应该就完美啦。