記一次Promise在api介面合併中的實踐

huaianfox發表於2019-03-07

寫在前面的話

  1. 本篇水文發出來之後,有朋友反饋文筆太差,的確是作者的鍋,碼字水平目前就這麼高,只能委屈大家看這篇辣眼睛的文字了,文筆只能慢慢改善。
  2. 還有朋友反饋看懵的,仔細想想也是作者的鍋,沒有表達清楚,修改重發。
  3. 澄清一下本文和Promise.all沒有半毛錢關係,如果讓大家誤會,見諒。
  4. 有朋友希望快速瀏覽能有一句總結,不想看程式碼。這裡解釋一下,其實總結就包含在下文合併介面的解釋裡,本文要解決的業務相對比較小眾,脫離場景談總結也沒啥意義,不看業務場景和程式碼,本文真的沒有任何的價值,主要是記錄開發業務的一個思路。

關於介面合併(不知道有沒有專門的術語,暫且如此稱呼)在這裡解釋一下,本文所指的是頁面初始化載入資料是一個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: 每天水一篇,生活樂無邊。

相關文章