寫在前面的話
- 本篇水文發出來之後,有朋友反饋文筆太差,的確是作者的鍋,碼字水平目前就這麼高,只能委屈大家看這篇辣眼睛的文字了,文筆只能慢慢改善。
- 還有朋友反饋看懵的,仔細想想也是作者的鍋,沒有表達清楚,修改重發。
- 澄清一下本文和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: 每天水一篇,生活樂無邊。