Promise實踐 封裝微信小程序接口

相信不少開發者都遇到過回調地獄的問題。因爲微信小程序的API基本都是基於回調函數的異步操做,若是不使用其餘框架或者封裝API,特別是使用較多的wx.request(),基本很快就會遇到回調地獄的問題,維護起來十分痛苦。javascript

舉個例子

假設此時在正在開發一個社交小程序,其中有一個功能的是,小程序用戶在登陸後,能夠查看附近的人。前端

假設使用如下的實現思路,咱們經過wx.getLocation()獲取用戶當前位置,而後經過wx.request()請求後端數據。但在此以前須要登陸,參考以前官方文檔推薦的登陸方式,先調用wx.login()獲取code,再用wx.request()請求開發者服務器,成功返回自定義登陸態(通常爲access_token或其餘令牌形式),以後再用自定義登陸態請求業務數據。java

爲了方便看,我把官方文檔裏的登陸流程貼出來⬇️ git

img

思路肯定後,開始嘗試coding(如下代碼不建議看完)es6

/* 如下爲Page對象的方法 */

getNearby: function() {
  // 判斷是否已認證,可採用wx.checkSession()方案
  if (isAuth) {
  // TODO: 獲取業務數據
    return
  }
  
  // wx.login獲取code
  wx.login({
    success(res) {
      if (res.code) {
      
      // 獲取自定義登陸態
      wx.request({
        url,
        method, 
        headers,
        data,
        success(res) {
          // 請求成功
          if (res.statuCode === 200) {
            // 讀取響應體中的自定義登陸態
            let token = res.data.token
            // 保存自定義登陸態
            wx.setStorageSync("assess_token", token)
            
            // 獲取位置信息
            wx.getLocation({
              success(res) {
                let { latitude, longitude } = res
                
                // 請求業務數據
                wx.request({
                  url, 
                  method, 
                  header,
                  data: { latitude, longitude },
                  success(res) {
                    // 請求成功
                    if (res.statuCode === 200) {
                      let data = res.data
                      // 數據渲染到V層
                      this.setData({ list: data })
                    }
                    // 請求失敗
                    else if (res.statuCode === 400) {
                      // TODO
                    }
                    // 其餘錯誤狀況狀態碼處理
                    // TODO
                  }, 
                  fail(err) {
                    // 調用失敗處理
                  }
                })
                
              },
              fail(err) {
                // 調用失敗處理
              }
            })
          }
          // 請求失敗
          else if (res.statuCode == 400) {
            // TODO
          }
          // 其餘錯誤狀況的狀態碼處理
          },
          fail(err) {
            // 調用失敗處理
          }
        })
      } 
      else {
        // TODO
        // 登陸失敗
      }
    }, 
    fail(err) {
      // wx.login()調用失敗處理
      // TODO: ...
    }
  }) 
}

複製代碼

回調地獄出現了。氣功波代碼,別說別人,就連本身看都會以爲噁心。小程序

某天英明的產品經理站了出來,說咱們能夠加點XXXXX,你可能還得找個地方嵌套其餘微信接口或者多加幾個if else分支,到時候就找個地方哭吧。後端

解決方案

從某種意義上來講,當今風暴式的前端生態,仰仗於Node以及ES6+的出現。微信小程序

ES6後對於異步有多種解決方案。一種是採用generator/yield,但generator函數使用起來其實比較麻煩。另一種是採用Promise,相對比較簡單。ES7也能夠採用async/await,但本質上async/await也是基於Promise。下面介紹Promiseapi

Promise

Promise建立

建立Promise很簡單,Promise自己是一個構造函數。經過new建立。構造函數的參數爲一個回調函數,回調函數有兩個參數爲resolvereject(無需手動維護)。resolvereject是用來改變狀態。關於狀態放到後邊講。數組

// Promise實例的建立
let p = new Promise((resolve, reject) => {
  // TODO
})
複製代碼

Promise有個缺點,一旦建立便會馬上執行。因此通常會用一個函數進行包裝。

let getPromise = () => {
  return new Promise((resolve, reject) => {
    // TODO 
  })
}
複製代碼

Promise狀態

Promise實例有三種狀態,pendingresolvedrejected,Promise實例建立後就會處於pending狀態。回調函數中的resolvereject就是用來改變Promise實例狀態的。當調用resolve時,Promise實例會從pending變成resolved狀態,表示成功。當調用reject時,Promise實例會從pending變成rejected狀態,表示失敗。

let getPromise = () => {
  return new Promise((resolve, reject) => {
    // TODO 
    // 處理結果
    if (result) {
      resolve(successObject)
    } 
    else {
      reject(error)
    }
  })
}
複製代碼

經常使用方法

最經常使用的方法爲then()catch()這兩個方法,經過then()的傳遞效用就能夠解決回調地獄的問題。

其中then()可接收兩個參數,都是回調函數,第一個回調函數用來處理resolved狀態,參數爲Promise實例調用resolve傳遞的成功對象。第二回調函數用來處理rejected狀態,參數爲調用Promise實例調用reject傳遞的錯誤對象。

實際中then()咱們通常只用來處理resolved的狀況,即只傳遞第一個回調函數。對於rejected狀況更可能是採用catch()統一處理。

let getPromise = () => {
  return new Promise((resolve, reject) => {
    // TODO 
    // 處理結果
    if (result) {
      resolve(successObject)
    } 
    else {
      reject(error)
    }
  })
}

getPromise()
  .then(res => {
    console.log(res)
    // TODO
  })
  .catch(err => {
    //TODO
  })
複製代碼

使用then()方法能夠繼續返回一個Promise對象,經過return一個新的Promise,能夠持續的向下傳遞。

getPromise()
  .then(res => {	//第一層Promise
    console.log(res)
    // TODO
    return getPromise()
    )
  .then(res => {	// 第二層Promise
    console.log(res)
    // TODO
  })
  .catch(err => {
    // TODO
  })
複製代碼

其餘經常使用方法有諸如Promise.all()Promise.race()。當須要等待多個Promise結果時會採用。兩個方法都是接收一個由Promise組成的對象數組。使用Promise.all()時,只有當所有的Promise對象所有resolved Promise.all()狀態纔是resolved。而Promise.race()只需有一個Promise對象爲resolved時,其狀態就爲resolved

更多方法可閱讀相關文檔。

封裝小程序接口

學習了Promise基礎,經過封裝異步操做,使用Promise鏈就能夠解決回調地獄問題。

由於wx.request()使用頻率比較高,先對wx.request()封裝。

/* 能夠將公用的方法掛在app.js中 */

request: function(method, url, header, data) {
  return new Promise((resolve, reject) => {
    wx.request({
      method, 
      url, 
      header, 
      data,
      success(res) {
        resolve(res)
      },
      fail(err) {
        reject(err)
      }
    })
  })
}
複製代碼

基本框架就這樣,咱們能夠進一步修改,好比請求url的基礎路徑,添加一些公用的header,針對狀態碼作一些全局處理等。

request: function(method, url, header = {}, data = {}) {
  // 啓動時可將storage中的令牌掛到app.js 
  let token = app.assess_token
  if (token) {
    header["Authorization"] = token
  }
  return new Promise((resolve, reject) => {
    wx.request({
      method, 
      url: "https://api.domain.com/v1" + url,
      header, 
      data,
      success(res) {
        // 請求成功
        if (res.statusCode === 200) {
          resolve(res)
        }
        // 請求成功無響應體
        else if (res.statusCode === 204) {
          /* 可作一些成功提示, 如調用wx.showToast()、wx.showModal()或自定義彈出層等 */
          resolve(res)
        }
        // 未認證
        else if (res.statusCode === 401) {
          /* 可作一些錯誤提示,或者直接跳轉至登陸頁面等 */
          reject(res)
        }
        else if (res.statusCode == 400) {
        /* 可作一些錯誤提示*/
          reject(res)
        }
        else if (res.statuCode === 403) {
          /* 無權限錯誤提示*/
          reject(res)
        }
        // ...其餘狀態碼處理
      },
      fail(err) {
        /* 可作一些全局錯誤提示,如網絡錯誤等 */
        reject(err)
      }
    })
  })
}
複製代碼

封裝以後,舉個例子,發送請求就能夠修改成

/* 方法體中 */
let app = getApp()

app.request("POST", "/auth", {}, { username, password })	 
  .then(res => {  // 第一層請求
    // TODO 成功處理
    return app.request("GET", "/goods", {}, {})
  })
  .then(res => {	// 第二層請求
    // TODO 成功處理
    // 渲染視圖
  })
  .catch(err => {
    // TODO 錯誤處理
  })
複製代碼

封裝一下其餘的微信接口

/* 能夠將公用的方法掛在app.js中 */
wxLogin: function() {
  return new Promise((resovle, reject) => {
    wx.login({
      success(res) {
        if (res.code) {
          resovle(res)
        }
        else {
          reject({ message: "登陸失敗" })
        }
      },
      fail(err) {
        reject(err)
      }
    })
  })
}

getLocation: function() {
  return new Promise((resolve, reject) => {
    wx.getLocation({
      success(res) {
        resolve(res)
      },
      fail(err) {
        reject(err)
      }
    })
  })
}
複製代碼

對於最初的例子,能夠就修改成

/* Page對象的方法 */

getNearby: function() { 
  // 判斷是否已認證,可採用wx.checkSession()方案
  if (isAuth) {
    // TODO: 獲取業務數據
    return
  }
  
  app.wxLogin()
    .then(res => {
      // 將code發送給開發者服務器,獲取自定義登陸態
      return app.request("POST", "/auth", {}, { code, res.code })
    })
    .then(res => {
      // 保存自定義登陸態
      setStorage("access_token", res.data.access_token)
      // TODO: 其餘登陸成功操做... 
      return app.getLocation()
    })
    .then(({ latitude, longitude }) => {
      let url = "/nearby?latitude=" + latitude + "&longitude=" + longitude
      return app.request("GET", url)
    })
    .then(res => {
      // TODO: 數據處理
      let data = res.data
      // 渲染視圖層
      this.setData({ data })
    })
    .catch(err => {
      // TODO 錯誤處理
    })
    
}
複製代碼

以後如有需添加新的請求或者其餘異步操做,直接在Promise鏈上操做就好了。

推薦閱讀

es6.ruanyifeng.com/#docs/promi…

devdocs.io/javascript-…

相關文章
相關標籤/搜索