写在前面的话
- 本篇水文发出来之后,有朋友反馈文笔太差,的确是作者的锅,码字水平目前就这么高,只能委屈大家看这篇辣眼睛的文字了,文笔只能慢慢改善。
- 还有朋友反馈看懵的,仔细想想也是作者的锅,没有表达清楚,修改重发。
- 澄清一下本文和Promise.all没有半毛钱关系,如果让大家误会,见谅。
- 有朋友希望快速浏览能有一句总结,不想看代码。这里解释一下,其实总结就包含在下文合并接口的解释里,本文要解决的业务相对比较小众,脱离场景谈总结也没啥意义,不看业务场景和代码,本文真的没有任何的价值,主要是记录开发业务的一个思路。
关于接口合并(不知道有没有专门的术语,暂且如此称呼)在这里解释一下,本文所指的是页面初始化加载数据是一个api接口,而加载更多数据的是另一个api接口,前一个接口肯定会调用,第二个接口不一定会被调用(用户触发),但是我们把两个调用接口封装起来,公用一个业务逻辑,作者比较懒不想给两个接口分别写业务逻辑。
一. 前言
上次作者在个人项目中遇到的post预检请求bug,水了一篇小文《记一次跨域post请求数据之preflight request》,本文也只是记录在特定项目中如何抽取业务逻辑,封装两个api接口公用一段业务逻辑的思路,对读者朋友们有所启发,那就最好不过了,有什么问题或者错漏之处欢迎大家提出来分享,作者此文权当抛砖引玉。
关于接口合并,作者在项目开发文档中也有描述,有兴趣的可以去瞅瞅。
二. 猫眼API接口分析
脱离业务谈编码就是耍流氓。下面简单介绍一下猫眼的接口,其中我们发现在猫眼的两个页面中可以使用接口合并,现在以猫眼正在热映页面的两个api为例。
1 初始化获取当前热映电影列表
以下都将以 api_1 指称 初始化获取当前热映电影列表api接口
1.1说明
信息 | 说明 |
---|---|
功能 | 初始化获取电影信息 |
URL | //m.maoyan.com/ajax/movieOnInfoList |
格式 | JSON |
HTTP METHOD | GET |
1.2 请求参数
参数 | 类型 | 必选 | 说明 |
---|---|---|---|
token | String | false | 登录之后的凭证 |
1.3返回字段
字段 | 类型 | 说明 |
---|---|---|
movieList | Array | 电影列表(默认一次返回10条) |
total | Number | 电影总数目, total >= movieList.length |
movieIds | Array | 所有电影ID,总数同total,后续请求更多电影时必须依赖它们 |
coming | Array | 更多电影列表,第一次请求必定是空 |
1.4 接口示例
//m.maoyan.com/ajax/movieOnInfoList?token
{
"coming": [],
"stid": "576591972453269000",
"movieIds": [247295, 410629, 1206605, 248906, 341139, 1250341, 1218091, 344869, 1243239, 580298, 907653],
"movieList": [
"同下方获取当前热映更多电影列表接口返回的coming字段"
],
"stids": [
{"movieId": 247295, "stid": "576591972453269000_a247295_c0"}
],
"total": 11
}
复制代码
2 获取当前热映更多电影列表
以下都将以 api_2指称 获取当前热映更多电影列表api接口
2.1 说明
信息 | 说明 |
---|---|
功能 | 获取hot更多电影列表 |
URL | //m.maoyan.com/ajax/moreComingList |
格式 | JSON |
HTTP METHOD | GET |
2.2 请求参数
参数 | 类型 | 必选 | 说明 | 列子 |
---|---|---|---|---|
token | String | false | 登录之后的凭证 | |
movieIds | String | true | 请求的电影ID,依赖初始化接口的接口返回字段movieIds | "1214652,1229799,1251606" |
2.3 返回字段
字段 | 类型 | 说明 |
---|---|---|
coming | Array | 更多电影列表 |
2.4 接口示例
//m.maoyan.com/ajax/moreComingList?token=&movieIds=1214652%2C1229799%2C1251606%2C1215114
{
"coming": [
{
"id": 1214652,
"comingTitle": "2月22日 周五",
"globalReleased": true,
"haspromotionTag": false,
"img": "http://p0.meituan.net/w.h/movie/979266668d0e94dc83956a70d22b4eaa184105.jpg",
"nm": "朝花夕誓-于离别之朝束起约定之花",
"preShow": false,
"rt": "2019-02-22",
"sc": "9.2",
"showInfo": "今天10家影院放映21场",
"showst": "3",
"star": "石见舞菜香,入野自由,茅野爱衣",
"version": "",
"wish": 76220,
"wishst": 0,
},
...略
]
}
复制代码
上面两大坨数据,就是作者整理的api接口文档,仔细观察两个api接口的返回字段,都有一个coming字段,作者最初的灵感也是来自于它们,api_1接口的数据列表放在movieList字段中,我们下面就将以Promise来处理coming和movieList。
有朋友关注api_2接口依赖于api_1接口,猫眼的api就是这么设计的,api_1接口返回了部分电影列表、全部的电影id和电影总数,api_2接口请求只需传递电影id就可以了。其他公司设计的api接口请求参数可能就是offset和limit。
三. 方案
要进行接口合并,无非要解决两个问题, 判断接口、处理数据
1 判断接口
api_2接口请求数据的时候必定需要知道请求的是那些电影的ID,那么我们肯定要在本地定义一个offset作为数据的偏移量,作者的项目是vue写的,就放在了vue的组件实例上了。我们将offset设为0,第一次请求时offset必定为0,我们就将offset的值作为判断接口的依据。
下面直接上代码
/***
* 业务逻辑部分
* 1. isFirst判断是否第一次请求
* 2. getInfoListAction(isFirst) 得到最终的api操作函数 getMovieInfoList
* 关于 getInfoListAction请参看下文 @src src\api\index.js
***/
import { getInfoListAction } from '@/api'
const { offset, limit, total } = this
const isFirst = offset === 0
const getMovieInfoList = getInfoListAction(isFirst)
getMovieInfoList(params).then(data => {
// ....数据处理此处略,详见下文
})
复制代码
难道直接用if-esle来硬编码判断?作者当然不会这么糊弄大家了。
/**
* @addr src/api/index.js
* @ getMovieOnInfoList 初始api的操作函数
* @ getMoreComingList 加载更多数据的操作函数
* @ getInfoListAction通过上文的isFirst作为参数调用来判断返回 getMovieOnInfoList还是 getMoreComingList(也就是上文提到的getMovieInfoList)
* 关于 getDataByAction 参看下文 @addr src/util/index.js
**/
import request from '@/util/request'
import { getDataByAction } from '@/util'
const getMovieOnInfoList = request('/movieOnInfoList')
const getMoreComingList = request('/moreComingList')
export const getInfoListAction = getDataByAction(getMovieOnInfoList, getMoreComingList)
/***
* @addr src/util/index.js
* @getDataByAction 使用函数柯里化,接受两个操作函数返回一个新函数,在业务逻辑中返回最终的api操作函数
**/
export const getDataByAction = (initAction, nextAction) => (isFirst) => isFirst ? initAction : nextAction
// @addr src/util/request.js
import Axios from 'axios'
let baseURL = process.env.VUE_APP_URL
const defaultConfig = {
baseURL
}
const STATUS_CODE = 200
const instance = Axios.create(defaultConfig)
const request = (url, method = 'get') => (params) => {
return instance({
url,
method,
...params
}).then(resp => {
if (resp.status === STATUS_CODE) {
return resp.data
}
})
}
export default request
复制代码
请忽略作者的request函数的丑陋封装,没有做错误处理,(逃
2 数据处理
由上文可知,我们最终的api调用函数调用之后其实是返回了一个Promise{<resolve>:data}
我们在vue组件实例上定义了movieList存放数据,movieIds存放第一次返回时movieIds字段的数据,total数据总数。
// 接上文的省略的代码部分
// 暂时忽略params参数,下文有处理详解
/**
* 1. 在promise.then的函数中,我们从data数据里取 movieIds, movieList, coming, total字段
* 2.1 我们以movieIds判断是第一次调用api接口(其他字段也可以,这里先偷懒),那么我们赋值需要的数据 movieIds,total,直接返回movieList数据.
* 2.2 如果2.1没有执行,那么肯定是加载更多数据的接口api_2,我们直接返回coming字段
* 3. 从2.1、2.2我们获得了最后的数据Array,判断数据的长度,更新offset偏移量和movieList数据
* ps: setImgSize是处理图片的函数,不必理会
**/
getMovieInfoList(params).then(data => {
const { movieIds, movieList, coming, total } = data
if (movieIds) {
this.movieIds = movieIds
this.total = total
return movieList
}
return coming
}).then(data => {
if (data.length) {
this.offset += data.length
this.movieList.push(...setImgSize(data))
$state.loaded()
} else {
$state.complete()
}
})
复制代码
3 参数处理
const { offset, limit, total } = this
const isFirst = offset === 0
if (offset && offset > total) return
const movieIds = this.movieIds
.slice(offset, offset + limit)
.join()
const params = { params: { ...this.params, movieIds } }
复制代码
结尾
水到现在终于要结束了,挤一挤好像也没啥干货,关于接口合并,智者见智,万一以后改接口爆炸了也说不定,作者只是记录下当前遇到类似情况的一种处理方案,或者有更好的方案欢迎大家分享,行文错漏、改善之处欢迎提出来探讨。
Tips: 每天水一篇,生活乐无边。