1. 程式人生 > >async/await基本理解及專案案例(結合Promise)

async/await基本理解及專案案例(結合Promise)

ES2017 標準引入了 async 函式,使得非同步操作變得更加方便。

1.async/await場景

這是一個用同步的思維來解決非同步問題的方案,當前端介面呼叫需要等到介面返回值以後渲染頁面時。

2.名詞解釋

async

async的用法,它作為一個關鍵字放到函式前面,用於表示函式是一個非同步函式,因為async就是非同步的意思, 非同步函式也就意味著該函式的執行不會阻塞後面程式碼的執行,async 函式返回的是一個promise 物件,可以使用then方法添加回調函式。當函式執行的時候,一旦遇到await就會先返回,等到非同步操作完成,再接著執行函式體內後面的語句。

await

await的含義為等待。意思就是程式碼需要等待await後面的函式執行完並且有了返回結果之後,才繼續執行下面的程式碼。這正是同步的效果。

3.基本講解

簡單案例:

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.resolve() 方法把它轉化成一個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)
})

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, 我們可以看到,寫非同步程式碼就像寫同步程式碼一樣了,再也沒有回撥地域了。

Vue專案案例

普通案例:

methods: {
     getLocation(phoneNum) {
         return axios.post('/mm介面', {
             phoneNum
         })
     },    
     getFaceList(province, city) {
         return axios.post('/nn介面', {
             province,
             city
         })
     },  
     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 來改造一下。

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);
                }
            }

Vue專案案例(封裝)

http.js

'use strict'

import axios from 'axios'
import qs from 'qs'

axios.interceptors.request.use(config => {
  // loading
  return config
}, error => {
  return Promise.reject(error)
})

axios.interceptors.response.use(response => {
  return response
}, error => {
  return Promise.resolve(error.response)
})

function checkStatus (response) {
  // loading
  // 如果http狀態碼正常,則直接返回資料
  if (response && (response.status === 200 || response.status === 304 || response.status === 400)) {
    return response
    // 如果不需要除了data之外的資料,可以直接 return response.data
  }
  // 異常狀態下,把錯誤資訊返回去
  return {
    status: -404,
    msg: '網路異常'
  }
}

function checkCode (res) {
  // 如果code異常(這裡已經包括網路錯誤,伺服器錯誤,後端丟擲的錯誤),可以彈出一個錯誤提示,告訴使用者
  if (res.status === -404) {
    alert(res.msg)
  }
  if (res.data && (!res.data.success)) {
    alert(res.data.error_msg)
  }
  return res
}

export default {
  post (data,url) {
    return axios({
      method: 'post',
      url: url,
      data: qs.stringify(data),
      timeout: 10000,
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
      }
    }).then(
      (response) => {
        return checkStatus(response)
      }
    )
  },
  get (url, params) {
    return axios({
      method: 'get',
      baseURL: 'https://cnodejs.org/api/v1',
      url,
      params, // get 請求時帶的引數
      timeout: 10000,
      headers: {
        'X-Requested-With': 'XMLHttpRequest'
      }
    }).then(
      (response) => {
        return checkStatus(response)
      }
    ).then(
      (res) => {
        return checkCode(res)
      }
    )
  }
}

api.js

export default {
  getCode: 'http://127.0.0.1:8888/.....'
}

auth.vue

import http from '../../utils/http'
import api from '../../utils/api'

   methods: {
      fetchData: async function () {
        var that = this
        var code = Store.fetchYqm();
        let params = {
          inviteCode: code
        }
        const response = await http.post(params,api.getCode)
         var resJson = response.data;
        
      }
}

 

某公司的專案案例