使用async/await來處理非同步(vue+node的例項)
https://www.jianshu.com/p/ab767311589d
使用async/await來處理非同步(vue+node的例項)
用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('我是先執行的');
image.png
好像沒有什麼用,別急,首先我們看到hello()返回的是一個promise物件,其次它好像沒有去非同步執行。
async非同步函式返回的是一個promise物件,如果要獲取到promise返回值,我們就應該使用.then方法。
async function hello(){
return 'hello world';
}
hello().then(result=>{
console.log(result);
})
console.log('我是先執行的');
image.png
然後就沒問題啦,一個簡單的非同步函式就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));
image.png
如果函式內部發生錯誤,promise物件有一個catch方法進行捕獲。
hello(0).catch(err=>{
console.log(err);
})
image.png
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檔案,這個檔案的目錄
image.png
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,可以看到頁面如下,展示正確
image.png
我們再來動態獲取充值面值。當用戶點選按鈕時,我們首先要根據手機號得到省和市,所以寫一個方法來發生請求獲取省和市,方法命名為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啦,這應該就完美啦。