前端項(xiàng)目請(qǐng)求層封裝過程

調(diào)用 ajax 取請(qǐng)求后端數(shù)據(jù)是項(xiàng)目中最基礎(chǔ)的功能。但是如果每次直接調(diào)用底層的瀏覽器 api 去發(fā)請(qǐng)求則非常麻煩『桑現(xiàn)在來分析一下怎么封裝這一層宠纯,看看有哪些基礎(chǔ)問題需要考慮。本文底層使用 fetch 层释,如果你使用 XMLHttpRequest 甚至第三方庫(譬如:axios)封裝過程都是大同小異的婆瓜。

封裝重復(fù)代碼

對(duì)于同一個(gè)項(xiàng)目通常來說請(qǐng)求參數(shù)有很多重復(fù)的內(nèi)容,譬如 url 的拼接,http head 的設(shè)置廉白。假設(shè)我們調(diào)用的是 RESTful 接口个初,通常我們需要變動(dòng)的有:1. 請(qǐng)求 url 的 path 部分;2. 參數(shù)猴蹂;3. 請(qǐng)求 method院溺;4. 成功/失敗回調(diào)函數(shù)。我們看下把重復(fù)代碼封裝成一個(gè) ApiSender 的示例代碼:

const URL_PREFIX = 'xxx';

let ApiSender = {
  send( options ) {
    let {
      path,
      params,
      method,
      success,
      fail
    } = options;

    let url = URL_PREFIX + path;
    if ( method==='GET' ) {
      url += ('?'+toQueryString( params ));
    }
    let requestBody;
    if ( method==='POST' ) {
      requestBody = params;
    }

    fetch( url, {
      method: method,
      // 這里假設(shè)我們項(xiàng)目請(qǐng)求頭固定這兩個(gè)
      headers: {
        'Accept': 'application/json, text/javascript, */*; q=0.01',
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
      },
      credentials: 'include',
      body: requestBody
    } ).then( function(response){
      let resultJson = response.json();
      if ( /* 判斷返回沒有錯(cuò)誤 */ ) {
        success && success( resultJson );
      } else {
        fail && fail( resultJson.error );
      }
    } );
  }
}

使調(diào)用可讀性更好

以上封裝了一個(gè) ApiSender磅轻,調(diào)用的時(shí)候如下:

ApiSender.send( '/resource', 'GET', {
  pageSize: 10,
  pageNo: 1
}, function( result ){
  // 對(duì)結(jié)果進(jìn)行處理
}, function( error ){
  alert( error )
} )

通過傳遞回調(diào)函數(shù)的方式珍逸,可讀性性不是很好(當(dāng)然這是一個(gè)仁者見仁的問題)。我們把返回改成 Promise聋溜。因?yàn)槲覀冇玫氖?fetch谆膳,它直接返回的就是 Promise,比較好改撮躁。如果你底層用的是 XMLHttpRequest漱病,那么可以自行把調(diào)用 XMLHttpRequest 的代碼封裝在一個(gè) Promise 中返回。

let ApiSender = {
  send( options ) {
    let {
      path,
      params,
      method,
      success,
      fail
    } = options;

    let url = URL_PREFIX + path;
    if ( method==='GET' ) {
      url += toQueryString( params );
    }
    let requestBody;
    if ( method==='POST' ) {
      requestBody = params;
    }

    return fetch( url, {
      method: method,
      // 這里假設(shè)我們項(xiàng)目請(qǐng)求頭固定這兩個(gè)
      headers: {
        'Accept': 'application/json, text/javascript, */*; q=0.01',
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
      },
      credentials: 'include',
      body: requestBody
    } ).then( function(response){
      return response.json()
    } );
  }
}

調(diào)用的時(shí)候代碼就變成:

ApiSender.send( '/resource', 'GET', {pageSize:10,pageNo:1} ).then( function(result){
  if ( /* 判斷返回沒有錯(cuò)誤 */ ) {
    // 處理結(jié)果
  } else {
    // 提示錯(cuò)誤
  }
} )

從調(diào)用者角度抽象返回值

上面代碼有一個(gè)問題把曼,對(duì)于 ApiSend 的調(diào)用者來說杨帽,他需要直接處理接口返回值,判斷是否成功祝迂。如果接口返回對(duì)象比較簡(jiǎn)單還好睦尽,如果非常復(fù)雜,那么調(diào)用者就很頭疼型雳,舉個(gè)例子,我碰到過如下的接口返回值:

{
  content: {
    result: {
      errorCode: 1,
      errorMessage: '',
      isSuccess: true
    },
    data: {}|[] // 真正的可用數(shù)據(jù)
  },
  a: { // 有特征的字段名我做了簡(jiǎn)化山害,使用了a纠俭,ab這樣的字段名。a 這個(gè)字段內(nèi)容是 api 網(wǎng)關(guān)層包裝的浪慌。
    code: 1,
    ab: [ {
      code: 1
    } ]
  }
}

如何判斷這個(gè)返回值是成功的呢冤荆?

let result = { /* 上面那個(gè)對(duì)象 */ }
if (
  result.a &&
  result.a.code === 0 &&
  result.a.ab &&
  result.a.ab[ 0 ] &&
  result.a.ab[ 0 ].code === 0
) {
  if (
    result.content &&
    result.content.result &&
    result.content.result.isSuccess === true
  ) {
    // 處理結(jié)果 result.content.data
  }
}

你想象下,作為 ApiSender 的調(diào)用方权纤,會(huì)希望得到什么結(jié)果钓简?執(zhí)行正確的時(shí)候獲得接口返回的數(shù)據(jù),執(zhí)行異常的時(shí)候獲得錯(cuò)誤信息汹想。我不希望調(diào)用一個(gè)方法外邓,需要通過復(fù)雜地解析返回值來判斷是否成功。所以最直觀的就是把錯(cuò)誤封裝成一個(gè)很直觀的返回值:

let ApiSender = {
  send( options ) {

    /* 代碼省略掉了 */

    return fetch( /* 參數(shù)也省略掉了 */ ).then( function(response){
      let result = response.json();
      if ( isSuccessResult(result) ) {
        return [ null, result.content.data ]
      } else {
        let error = parseError( result );
        return [ error, null ];
      }
    } );
  }
}

那么調(diào)用方對(duì)結(jié)果的判斷就非常方便了:

ApiSender.send( '/resource', 'GET', {pageSize:10,pageNo:1} ).then( function([error,data]){
  if ( !error ) {
    // 處理結(jié)果 data
  } else {
    alert( error ); // error 的格式大家可以自行定義古掏,各個(gè)項(xiàng)目各有不同
  }
} );

面向切面需要做些什么

以上一個(gè)比較基礎(chǔ)且簡(jiǎn)潔的封裝就做好了损话,但是現(xiàn)實(shí)中有些基礎(chǔ)功能是經(jīng)常需要的,譬如請(qǐng)求日志,請(qǐng)求錯(cuò)誤報(bào)錯(cuò)統(tǒng)一處理丧枪。如果這些代碼需要調(diào)用方來做光涂,一來代碼重復(fù),二來譬如日志應(yīng)該是調(diào)用方不感知的一個(gè)功能拧烦。所以我們對(duì)代碼進(jìn)一步進(jìn)行優(yōu)化忘闻,加入這些功能:

let ApiSender = {
  send( options ) {

    /* 代碼省略掉了 */

    return fetch( /* 參數(shù)也省略掉了 */ ).then( function(response){
      let result = response.json();
      // 記錄調(diào)用日志
      writeLog( options, result );

      if ( isSuccessResult(result) ) {
        return [ null, result.content.data ]
      } else {
        let error = parseError( result );
        // 界面報(bào)錯(cuò)
        MessageComponent.error( `${error.message}(${error.code})` );

        return [ error, null ];
      }
    } );
  }
}

日志你可以上傳服務(wù)器,也可以就本地 console恋博,日志記錄哪些內(nèi)容齐佳,參數(shù)如何都按各自的項(xiàng)目需求而定。如此的話交播,調(diào)用方就更簡(jiǎn)潔了:

ApiSender.send( '/resource', 'GET', {pageSize:10,pageNo:1} ).then( function([error,data]){
  if ( !error ) {
    // 處理結(jié)果 data
  }
} );

絕大多數(shù)情況下重虑,調(diào)用接口返回錯(cuò)誤是需要在頁面上提示錯(cuò)誤的,但是并不是所有情況都需要秦士。譬如非用戶觸發(fā)的行為缺厉,且請(qǐng)求返回的結(jié)果并不嚴(yán)重影響頁面操作或者流程。那么我們可以在調(diào)用 ApiSender 的時(shí)候加一個(gè)參數(shù)隧土,允許調(diào)用方跳過全局錯(cuò)誤處理:

let ApiSender = {
  send( options ) {

    /* 代碼省略掉了 */
    let skipErrorHandler = options.skipErrorHandler;

    return fetch( /* 參數(shù)也省略掉了 */ ).then( function(response){
      let result = response.json();
      // 記錄調(diào)用日志
      writeLog( options, result );

      if ( isSuccessResult(result) ) {
        return [ null, result.content.data ]
      } else {
        let error = parseError( result );

        // 傳了這個(gè)參數(shù)才跳過提针,不傳或者傳了非 true 值(當(dāng)然包括 false),都認(rèn)為不跳過
        if ( skipErrorHandler===true ) {
          // 界面報(bào)錯(cuò)
          MessageComponent.error( `${error.message}(${error.code})` );
        }
        
        return [ error, null ];
      }
    } );
  }
}

所以如果你希望自己處理錯(cuò)誤曹傀,調(diào)用的時(shí)候代碼就是:

ApiSender.send( '/resource', 'GET', {skipErrorHandler:true/*, 其他參數(shù) */} ).then( function([error,data]){
  if ( !error ) {
    // 處理結(jié)果 data
  } else {
    // 自行處理錯(cuò)誤
  }
} );

到這里為止辐脖,請(qǐng)求層的基本封裝算是比較完整了,不過最后有一個(gè)小點(diǎn)要考慮下皆愉,如果你在 fetch().then 傳入的回調(diào)函數(shù)中因?yàn)榉N種原因而拋出了異常(譬如某個(gè)字段沒有判空)嗜价。那么 ApiSender 的調(diào)用方是沒法感知的,程序直接就報(bào)錯(cuò)了幕庐。所以為了程序的健壯性久锥,我們最后再加一個(gè) catch:

let ApiSender = {
  send( options ) {

    /* 代碼省略掉了 */
    let skipErrorHandler = options.skipErrorHandler;

    return fetch( /* 參數(shù)也省略掉了 */ ).then( function(response){
      let result = response.json();
      // 記錄調(diào)用日志
      writeLog( options, result );
      if ( isSuccessResult(result) ) {
        return [ null, result.content.data ]
      } else {
        let error = parseError( result );
        // 傳了這個(gè)參數(shù)才跳過,不傳或者傳了非 true 值(當(dāng)然包括 false)异剥,都認(rèn)為不跳過
        if ( skipErrorHandler===true ) {
          // 界面報(bào)錯(cuò)
          MessageComponent.error( `${error.message}(${error.code})` );
        }
        
        return [ error, null ];
      }
    } ).catch( function(error){
      return [ error, null ];
    } );
  }
}

這樣一個(gè)對(duì)調(diào)用方友好瑟由,避免代碼重復(fù)的請(qǐng)求層就封裝好了。PS: 如果對(duì) Promise 的 api 不是很熟悉的話冤寿,可以先了解下歹苦,有助于更好的理解示例代碼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末督怜,一起剝皮案震驚了整個(gè)濱河市殴瘦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亮蛔,老刑警劉巖痴施,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡辣吃,警方通過查閱死者的電腦和手機(jī)动遭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來神得,“玉大人厘惦,你說我怎么就攤上這事×ú荆” “怎么了宵蕉?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)节榜。 經(jīng)常有香客問我羡玛,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮抖单,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘让歼。我一直安慰自己,他們只是感情好丽啡,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布谋右。 她就那樣靜靜地躺著,像睡著了一般补箍。 火紅的嫁衣襯著肌膚如雪改执。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天坑雅,我揣著相機(jī)與錄音天梧,去河邊找鬼。 笑死霞丧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的冕香。 我是一名探鬼主播蛹尝,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼悉尾!你這毒婦竟也來了突那?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤构眯,失蹤者是張志新(化名)和其女友劉穎愕难,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猫缭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年葱弟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猜丹。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芝加,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出射窒,到底是詐尸還是另有隱情藏杖,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布脉顿,位于F島的核電站蝌麸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏艾疟。R本人自食惡果不足惜来吩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汉柒。 院中可真熱鬧误褪,春花似錦、人聲如沸碾褂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽正塌。三九已至嘀略,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乓诽,已是汗流浹背帜羊。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸠天,地道東北人讼育。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像稠集,于是被迫代替她去往敵國和親奶段。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案剥纷,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,711評(píng)論 1 56
  • 你不知道JS:異步 第三章:Promises 在第二章痹籍,我們指出了采用回調(diào)來表達(dá)異步和管理并發(fā)時(shí)的兩種主要不足:缺...
    purple_force閱讀 2,072評(píng)論 0 4
  • 1.幾種基本數(shù)據(jù)類型?復(fù)雜數(shù)據(jù)類型?值類型和引用數(shù)據(jù)類型?堆棧數(shù)據(jù)結(jié)構(gòu)? 基本數(shù)據(jù)類型:Undefined、Nul...
    極樂君閱讀 5,527評(píng)論 0 106
  • 原文地址:http://es6.ruanyifeng.com/#docs/promise Promise 的含義 ...
    AI云棧閱讀 884評(píng)論 0 7
  • 話說現(xiàn)在的美色誘惑已經(jīng)沒有了棺克,但是美食誘惑,細(xì)心的人總會(huì)發(fā)現(xiàn)线定。 有人說女人=會(huì)吃+會(huì)睡+會(huì)花錢娜谊,豬=會(huì)吃+會(huì)睡,所...
    114號(hào)別墅閱讀 315評(píng)論 0 0