对api请求封装的探索和总结
第⼀份⼯作的时候我们⽼⼤让我封装下请求,我当即就说:封装什么?为什么要封装,本⾝⼈家的库就已经进⾏封装了啊,只需要⼏个参数就可以调⽤了,封装的还是要传⼀些参数的。嗯~当时还是有点理直⽓壮的,正所谓⽆知者⽆谓?当然最后我还是听⽼⼤的了,那时候我只是封装了⼏个默认参数吧?⽽后经过⼏年的历练,对api请求的封装也⼀直在升级,现在请陪着我来⼀起回顾下
为什么进⾏封装
简化使⽤成本。不同于库,封装是针对项⽬的,我们可以给定请求的主域名、请求头等默认值,减少使⽤请求时的需要传的参数和其他配置等
可以统⼀处理⼀些逻辑,例如对请求异常的提醒处理等
可以对请求进⾏⼀些改造以满⾜使⽤习惯
怎么封装
回调 VS PROMISE
很明显,回调容易陷⼊回调地狱,所以⽆论请求还是其他场景我们⽬前的编程⽅式基本都是推荐使⽤promise的,尤其是新的async/await 的引⼊更是让promise的编程⽅式更加优雅
总是被resolve
请求总是被resolve?为什么?如果不是,会怎样?
async function f() {
try {
ject('出错了');
} catch(e) {
...
}
...
}
正如上⾯这段代码,如果我们不加catch的话会怎样?该f函数后⾯的所有的代码都不会被执⾏,也就是说如果我们要保证代码的健壮性则必须给async/await函数增try/catch容错
那我们不⽤async了呗,确实是个不错的主意,但我必须提醒async的⼏点好处:
总是返回promise,这⼀点很有⽤,例如
if (check) {
return true
js获取json的key和value} else {
return apiPromise()
}
你是判断返回值的类型还是resolve true?⽽async则⽐较完美的解决了这类问题!
async的await能让异步代码看起来是同步的,代码的结构也更清爽,语义也更明确
我更相信你已经在⼤量使⽤async,所以,如果使⽤了async/await那么try/catch千万别忘记哦
即便是简单场景下不需要使⽤async,promise被拒绝也会有⼀些⼩问题,例如
api().then(res=>{
this.hideLoading()
...
}).catch(err=>{
this.hideLoading()
...
})
⽆论是否被成功resolve,都要执⾏的⼀些代码需要在两处书写
所以,你想推介什么?
封装的api请求总是被resolve,这样是不是就没必要关⼼reject了?也就不⽤管刚才那⼀堆问题了,是
不是很爽??不对啊,总是有异常情况的啊,难道不管了?也resolve啊!?添加字段区分就⾏了啊,是不是很聪明??
确认请求是完全理想的正确
什么意思?我们先回想下⾃⼰是否曾⼤量写过这样的代码,如果没有请忽略
api().then(res=>{
this.hideLoading()
if (de !== 0) {
...
}
...
})
因为现在很多后端因监控运⾏状态等原因都不直接返回异常的http状态码,⽽是以各种code来标⽰是否处理成功,所以200的请求不⼀定是真正的请求完成,那校验code就成为必须的了
像真正的api那样
api有点被乱⽤了,api请求的api是后台提供的业务服务接⼝,抛去这⼀种,我们脑中正常的api是什么样⼦的?是不是像这
样array.push(1),是预先定义的函数,是不需要关⼼内部实现的,所以请把api请求也封装成像真正的api那样,简单好⽤,随处可⽤
⾄此,个⼈关于对封装api请求的思想基本都阐述了,我们来看看代码实现(基于⼩程序项⽬,供参考,核⼼代码⽤===============标⽰)
源码参考
先来看看最终你⽤的爽不爽
// 例如后台⽂档是这样的
// curl --request POST \
// --url '/controller/fun' \
// --header 'Content-Type: application/json' \
// --data '{
// "page":1
// }'
// 你只需要这样
this.hideLoaing()
if (Type) {
... // 异常处理
}
.
.. // 正常逻辑
})
// async⽅式
async function() {
const res = ller.fun({page: 10})
this.hideLoaing()
if (Type) {
... // 异常处理
}
... // 正常逻辑
}
⽬录结构
api
├── doRequest.js // 封装的请求⽅法
├── index.js // ⽣成api和export请求⽅法等
├── inject.js //
├── renewToken.js // 重新获取token
└── serviceJson.js // 供⽣成API的配置
doRequest.js // 封装的请求⽅法
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) => !_code
export async function doRequestWithCheckSession(data = {}, opts) {
const opt = Object.assign({needToken: true}, opts)
if (dToken === 'function') { // 是否需要鉴权有⼀定逻辑性,则可以将needToken配置设置为返回布尔值的函数,⽆参 dToken = dToken()
}
if (typeof wxSessionValid !== 'boolean') {
wxSessionValid = await checkWxSession() // 检查session是否有效
}
let jwt = wx.getStorageSync('jwt')
// 鉴权⽅式:业务侧的鉴权和对session有效性的鉴权
if (dToken && (!jwt || pire_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, dToken ? {httpOpt: {header: {Authorization: ken}}} : {})
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) => {
url,
data,
method,
...httpOpt,
success: (res) => { // 请求成功
if (checkDoRequestSuccess(res)) { // 服务端也处理成功
injectMap.forEach((val, key) => { // 匹配拦截规则
if (key.place(apiDomain, '')) !== -1) {
val()
}
})
resolve(res)
} else { // 服务端处理失败
needToast && wx.showToast({
title: ason || '请求出错,请稍后重试',
icon: 'none',
duration: 2000
})
resolve(Object.assign({
errType: 'server'
}, res))
}, 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'].ute) !== -1) {
return
}
success: function (res) {
const pathParamsStrArr = [] // 记录当前页⾯的路径参数
_.forOwn(page.options, (v, k) => {
pathParamsStrArr.push(`${k}=${v}`)
})
const path = `${ute}?${pathParamsStrArr.join('&')}`
wx.setStorageSync('badNetPagePath', path) // 记录被弱⽹中断的页⾯完整路径
if (resworkType === 'none') { // 如果是没有⽹络环境
url: '/pages/normal/network'
})
} else { // 弱⽹环境和其他异常情况
url: '/pages/normal/load'
})
}
}
})
}
核⼼index.js // ⽣成api和export请求⽅法等
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论