第一份工做的時候咱們老大讓我封裝下請求,我立即就說:封裝什麼?爲何要封裝,自己人家的庫就已經進行封裝了啊,只須要幾個參數就能夠調用了,封裝的仍是要傳一些參數的。嗯~當時仍是有點義正詞嚴的,正所謂無知者無謂😂固然最後我仍是聽老大的了,那時候我只是封裝了幾個默認參數吧🐶然後通過幾年的歷練,對api請求的封裝也一直在升級,如今請陪着我來一塊兒回顧下編程
很明顯,回調容易陷入回調地獄,因此不管請求仍是其餘場景咱們目前的編程方式基本都是推薦使用promise的,尤爲是新的async/await的引入更是讓promise的編程方式更加優雅json
請求老是被resolve?爲何?若是不是,會怎樣?小程序
async function f() {
try {
await Promise.reject('出錯了');
} catch(e) {
...
}
...
}
複製代碼
正如上面這段代碼,若是咱們不加catch的話會怎樣?該f函數後面的全部的代碼都不會被執行,也就是說若是咱們要保證代碼的健壯性則必須給async/await函數增try/catch容錯
那咱們不用async了唄,確實是個不錯的主意,但我必須提醒async的幾點好處:segmentfault
if (check) {
return true
} else {
return apiPromise()
}
複製代碼
你是判斷返回值的類型仍是resolve true?而async則比較完美的解決了這類問題!後端
即使是簡單場景下不須要使用async,promise被拒絕也會有一些小問題,例如微信小程序
api().then(res=>{
this.hideLoading()
...
}).catch(err=>{
this.hideLoading()
...
})
複製代碼
不管是否被成功resolve,都要執行的一些代碼須要在兩處書寫api
因此,你想推介什麼? 封裝的api請求老是被resolve,這樣是否是就不必關心reject了?也就不用管剛纔那一堆問題了,是否是很爽?😊不對啊,老是有異常狀況的啊,難道無論了?也resolve啊!😊添加字段區分就好了啊,是否是很聰明?😁promise
什麼意思?咱們先回想下本身是否曾大量寫過這樣的代碼,若是沒有請忽略bash
api().then(res=>{
this.hideLoading()
if (res.code !== 0) {
...
}
...
})
複製代碼
由於如今不少後端因監控運行狀態等緣由都不直接返回異常的http狀態碼,而是以各類code來標示是否處理成功,因此200的請求不必定是真正的請求完成,那校驗code就成爲必須的了微信
api有點被亂用了,api請求的api是後臺提供的業務服務接口,拋去這一種,咱們腦中正常的api是什麼樣子的?是否是像這樣array.push(1)
,是預先定義的函數,是不須要關心內部實現的,因此請把api請求也封裝成像真正的api那樣,簡單好用,隨處可用
至此,我的關於對封裝api請求的思想基本都闡述了,咱們來看看代碼實現(基於小程序項目,供參考,核心代碼用===============標示)
先來看看最終你用的爽不爽
// 例如後臺文檔是這樣的
// curl --request POST \
// --url 'http://user-interaction.ylf.org/controller/fun' \
// --header 'Content-Type: application/json' \
// --data '{ // "page":1 // }'
// 你只須要這樣
api.controller.fun({page: 10}).then(res=>{
this.hideLoaing()
if (res.errType) {
... // 異常處理
}
... // 正常邏輯
})
// async方式
async function() {
const res = await api.controller.fun({page: 10})
this.hideLoaing()
if (res.errType) {
... // 異常處理
}
... // 正常邏輯
}
複製代碼
目錄結構
api
├── doRequest.js // 封裝的請求方法
├── index.js // 生成api和export請求方法等
├── inject.js // 攔截器
├── renewToken.js // 從新獲取token
└── serviceJson.js // 供生成API的配置
複製代碼
import _ from '../lib/tools'
import injectMap from './inject'
import {api as constApi} from '../constVar'
import renewToken from './renewToken'
const apiDomain = constApi.domain
let getTokenPromise = ''// 只能同時存在一個實例
let wxSessionValid = null // 微信session_key的有效性
const checkWxSession = function () {
return new Promise(resolve => {
wx.checkSession({
success() {
resolve(true) // session_key 未過時,而且在本生命週期一直有效
},
fail() {
resolve(false) // session_key 已經失效,須要從新執行登陸流程
}
})
})
}
// 檢查業務層是否也處理成功,參數爲請求的返回值
const defaultCheckDoRequestSuccess = (res) => !res.data.error_code
export async function doRequestWithCheckSession(data = {}, opts) {
const opt = Object.assign({needToken: true}, opts)
if (typeof opt.needToken === 'function') { // 是否須要鑑權有必定邏輯性,則能夠將needToken配置設置爲返回布爾值的函數,無參
opt.needToken = opt.needToken()
}
if (typeof wxSessionValid !== 'boolean') {
wxSessionValid = await checkWxSession() // 檢查微信session是否有效
}
let jwt = wx.getStorageSync('jwt')
// 鑑權方式:業務側的鑑權和對微信session有效性的鑑權
if (opt.needToken && (!jwt || jwt.expire_after <= +new Date() || !wxSessionValid)) { // 須要受權,已過時,去續租
let jwt = ''
if (getTokenPromise) {
jwt = await getTokenPromise
} else {
getTokenPromise = renewToken()
jwt = await getTokenPromise
}
wxSessionValid = true
getTokenPromise = ''
wx.setStorageSync('jwt', jwt)
}
Object.assign(opt, opt.needToken ? {httpOpt: {header: {Authorization: jwt.token}}} : {})
return doRequest(opt.url, data, opt)
}
============================================================================================
/**
* 請求接口函數
* @param url
* @param data 請求body
* @param opt 具體配置見該函數的參數
* @returns {Promise<any>}
*
* !!! 老是被解決,永遠不會被拒絕,不過你能夠經過判斷是否有errType值來判斷是否請求OK
* errType === 'http' 是請求出錯
* errType === 'server' 是服務端處理出錯,須要checkDoRequestSuccess函數提供判斷邏輯
*/
export function doRequest(url, data, {
method = 'get', httpOpt = {}, needToken = true, needToast = true,
checkDoRequestSuccess = defaultCheckDoRequestSuccess
} = {}) {
return new Promise((resolve) => {
wx.request({
url,
data,
method,
...httpOpt,
success: (res) => { // 請求成功
if (checkDoRequestSuccess(res)) { // 服務端也處理成功
injectMap.forEach((val, key) => { // 匹配攔截規則
if (key.indexOf(url.replace(apiDomain, '')) !== -1) {
val()
}
})
resolve(res)
} else { // 服務端處理失敗
needToast && wx.showToast({
title: res.data.reason || '請求出錯,請稍後重試',
icon: 'none',
duration: 2000
})
resolve(Object.assign({
errType: 'server'
}, res))
}
},
fail: (err) => { // 請求失敗
resolve({
errType: 'http',
err
})
checkNetWorkAndSaveCurrentPath()
}
})
})
}
============================================================================================
// 檢查網絡問題和記錄當前頁面的路徑
function checkNetWorkAndSaveCurrentPath() {
/* eslint-disable no-undef */
const pages = getCurrentPages() // 獲取當前的頁面棧
const page = pages[pages.length - 1] // 當前的頁面
// 避免多個請求失敗形成多個弱網頁面棧,影響回跳
if (['pages/normal/network', 'pages/normal/load'].indexOf(page.route) !== -1) {
return
}
wx.getNetworkType({
success: function (res) {
const pathParamsStrArr = [] // 記錄當前頁面的路徑參數
_.forOwn(page.options, (v, k) => {
pathParamsStrArr.push(`${k}=${v}`)
})
const path = `${page.route}?${pathParamsStrArr.join('&')}`
wx.setStorageSync('badNetPagePath', path) // 記錄被弱網中斷的頁面完整路徑
if (res.networkType === 'none') { // 若是是沒有網絡環境
wx.redirectTo({
url: '/pages/normal/network'
})
} else { // 弱網環境和其餘異常狀況
wx.redirectTo({
url: '/pages/normal/load'
})
}
}
})
}
複製代碼
import serviceJson from './serviceJson'
import { doRequestWithCheckSession, doRequest } from './doRequest'
import _ from '../lib/tools'
import {api as constApi} from '../constVar'
const apiDomain = constApi.domain
const api = {}
serviceJson.forEach(obj => {
const keys = obj.url.replace(/\//g, '.')
obj.url = apiDomain + obj.url
_.set(api, keys, function (data) {
return doRequestWithCheckSession(data, obj)
})
})
/**
* 調用示例
* api.controller.fun({page: 10})
*
* 同時暴露出兩個封裝好的請求方法
*/
export default {
...api,
doRequest,
doRequestWithCheckSession
}
複製代碼
/**
* 項目請求配置
*
* 參數請前往 ./doRequest.js 查看doRequest函數說明,一下參數可能會出現變更而致使不許確
* needToken=true 是否須要token認證
* method=get 請求方法
* dataType=json dataType
* check 函數,參數爲請求的返回值,要求返回布爾值,true表明請求成功(後臺處理成功),false反之
*/
export default [
{'url': 'joke/content/list'}
]
複製代碼
// 請求hooks,當請求被匹配則執行預設的回調
// map的key爲 ./serviceJson.js 配置裏的url,value爲callback
// import _ from '../lib/tools'
const map = new Map()
export default map
複製代碼
import {doRequest} from './doRequest'
import _ from '../lib/tools'
import {api as constApi} from '../constVar'
const apiDomain = constApi.domain
function navToLogin(resolve) {
/* eslint-disable no-undef */
const pages = getCurrentPages()
const page = pages[pages.length - 1]
page.openLoginModal(resolve)
}
export default async function renewToken() {
// 確保有用戶信息
// 雖然只要有code便可換取用戶id,但一般咱們都須要
await new Promise(resolve => {
wx.getSetting({
success: (res) => {
// 若是用戶沒有受權或者沒有必要的用戶信息
if (!res.authSetting['scope.userInfo'] || !_.isRealTrue(wx.getStorageSync('userInfoRes').userInfo)) {
wx.hideLoading()
navToLogin(resolve)
} else {
resolve()
}
}
})
})
return new Promise((resolve) => {
wx.login({
success: res => {
login(res.code).then((jwt) => {
resolve(jwt) // resolve jwt
}) // 經過code進行登陸
},
fail(err) {
wx.showToast({
title: err.errMsg,
icon: 'none',
duration: 2000
})
}
})
})
}
/**
* 登錄,獲取jwt
* @param code
* @returns {Promise<any>}
*/
function login(code) {
return new Promise((resolve) => {
// 模擬登陸換取業務端的用戶信息和登陸信息,僅測試
doRequest(apiDomain + 'test/getToken', {code}, { needToast: false }).then(res => {
if (res.errType) {
// resolve('loginerr')
resolve({
'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9',
'expire_after': +new Date() + 1000 * 360 * 24
})
return
}
resolve({
'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9',
'expire_after': +new Date() + 1000 * 360 * 24
})
})
})
}
複製代碼
雖然是對API的思考,不只限小程序,但做爲同期的思考和總結,來波系列連接😄
開發微信小程序必需要知道的事
微信小程序之登陸態的探索
歡迎交流指正,謝謝