摘要:我們看下把重復代碼封裝成一個的示例代碼這里假設(shè)我們項目請求頭固定這兩個判斷返回沒有錯誤使調(diào)用可讀性更好以上封裝了一個,調(diào)用的時候如下對結(jié)果進行處理通過傳遞回調(diào)函數(shù)的方式,可讀性性不是很好當然這是一個仁者見仁的問題。
調(diào)用 ajax 取請求后端數(shù)據(jù)是項目中最基礎(chǔ)的功能。但是如果每次直接調(diào)用底層的瀏覽器 api 去發(fā)請求則非常麻煩?,F(xiàn)在來分析一下怎么封裝這一層,看看有哪些基礎(chǔ)問題需要考慮。本文底層使用 fetch ,如果你使用 XMLHttpRequest 甚至第三方庫(譬如:axios)封裝過程都是大同小異的。
封裝重復代碼對于同一個項目通常來說請求參數(shù)有很多重復的內(nèi)容,譬如 url 的拼接,http head 的設(shè)置。假設(shè)我們調(diào)用的是 RESTful 接口,通常我們需要變動的有:1. 請求 url 的 path 部分;2. 參數(shù);3. 請求 method;4. 成功/失敗回調(diào)函數(shù)。我們看下把重復代碼封裝成一個 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è)我們項目請求頭固定這兩個 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 ( /* 判斷返回沒有錯誤 */ ) { success && success( resultJson ); } else { fail && fail( resultJson.error ); } } ); } }使調(diào)用可讀性更好
以上封裝了一個 ApiSender,調(diào)用的時候如下:
ApiSender.send( "/resource", "GET", { pageSize: 10, pageNo: 1 }, function( result ){ // 對結(jié)果進行處理 }, function( error ){ alert( error ) } )
通過傳遞回調(diào)函數(shù)的方式,可讀性性不是很好(當然這是一個仁者見仁的問題)。我們把返回改成 Promise。因為我們用的是 fetch,它直接返回的就是 Promise,比較好改。如果你底層用的是 XMLHttpRequest,那么可以自行把調(diào)用 XMLHttpRequest 的代碼封裝在一個 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è)我們項目請求頭固定這兩個 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)用的時候代碼就變成:
ApiSender.send( "/resource", "GET", {pageSize:10,pageNo:1} ).then( function(result){ if ( /* 判斷返回沒有錯誤 */ ) { // 處理結(jié)果 } else { // 提示錯誤 } } )從調(diào)用者角度抽象返回值
上面代碼有一個問題,對于 ApiSend 的調(diào)用者來說,他需要直接處理接口返回值,判斷是否成功。如果接口返回對象比較簡單還好,如果非常復雜,那么調(diào)用者就很頭疼,舉個例子,我碰到過如下的接口返回值:
{ content: { result: { errorCode: 1, errorMessage: "", isSuccess: true }, data: {}|[] // 真正的可用數(shù)據(jù) }, a: { // 有特征的字段名我做了簡化,使用了a,ab這樣的字段名。a 這個字段內(nèi)容是 api 網(wǎng)關(guān)層包裝的。 code: 1, ab: [ { code: 1 } ] } }
如何判斷這個返回值是成功的呢?
let result = { /* 上面那個對象 */ } 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)用方,會希望得到什么結(jié)果?執(zhí)行正確的時候獲得接口返回的數(shù)據(jù),執(zhí)行異常的時候獲得錯誤信息。我不希望調(diào)用一個方法,需要通過復雜地解析返回值來判斷是否成功。所以最直觀的就是把錯誤封裝成一個很直觀的返回值:
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)用方對結(jié)果的判斷就非常方便了:
ApiSender.send( "/resource", "GET", {pageSize:10,pageNo:1} ).then( function([error,data]){ if ( !error ) { // 處理結(jié)果 data } else { alert( error ); // error 的格式大家可以自行定義,各個項目各有不同 } } );面向切面需要做些什么
以上一個比較基礎(chǔ)且簡潔的封裝就做好了,但是現(xiàn)實中有些基礎(chǔ)功能是經(jīng)常需要的,譬如請求日志,請求錯誤報錯統(tǒng)一處理。如果這些代碼需要調(diào)用方來做,一來代碼重復,二來譬如日志應該是調(diào)用方不感知的一個功能。所以我們對代碼進一步進行優(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 ); // 界面報錯 MessageComponent.error( `${error.message}(${error.code})` ); return [ error, null ]; } } ); } }
日志你可以上傳服務(wù)器,也可以就本地 console,日志記錄哪些內(nèi)容,參數(shù)如何都按各自的項目需求而定。如此的話,調(diào)用方就更簡潔了:
ApiSender.send( "/resource", "GET", {pageSize:10,pageNo:1} ).then( function([error,data]){ if ( !error ) { // 處理結(jié)果 data } } );
絕大多數(shù)情況下,調(diào)用接口返回錯誤是需要在頁面上提示錯誤的,但是并不是所有情況都需要。譬如非用戶觸發(fā)的行為,且請求返回的結(jié)果并不嚴重影響頁面操作或者流程。那么我們可以在調(diào)用 ApiSender 的時候加一個參數(shù),允許調(diào)用方跳過全局錯誤處理:
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 ); // 傳了這個參數(shù)才跳過,不傳或者傳了非 true 值(當然包括 false),都認為不跳過 if ( skipErrorHandler===true ) { // 界面報錯 MessageComponent.error( `${error.message}(${error.code})` ); } return [ error, null ]; } } ); } }
所以如果你希望自己處理錯誤,調(diào)用的時候代碼就是:
ApiSender.send( "/resource", "GET", {skipErrorHandler:true/*, 其他參數(shù) */} ).then( function([error,data]){ if ( !error ) { // 處理結(jié)果 data } else { // 自行處理錯誤 } } );
到這里為止,請求層的基本封裝算是比較完整了,不過最后有一個小點要考慮下,如果你在 fetch().then 傳入的回調(diào)函數(shù)中因為種種原因而拋出了異常(譬如某個字段沒有判空)。那么 ApiSender 的調(diào)用方是沒法感知的,程序直接就報錯了。所以為了程序的健壯性,我們最后再加一個 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 ); // 傳了這個參數(shù)才跳過,不傳或者傳了非 true 值(當然包括 false),都認為不跳過 if ( skipErrorHandler===true ) { // 界面報錯 MessageComponent.error( `${error.message}(${error.code})` ); } return [ error, null ]; } } ).catch( function(error){ return [ error, null ]; } ); } }
這樣一個對調(diào)用方友好,避免代碼重復的請求層就封裝好了。PS: 如果對 Promise 的 api 不是很熟悉的話,可以先了解下,有助于更好的理解示例代碼。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103262.html
摘要:用戶填寫所有信息后,提交給服務(wù)器,等待服務(wù)器的回應檢驗數(shù)據(jù),是一次性的。移除的元素包括純表現(xiàn)的元素對可用性產(chǎn)生負面影響的元素。網(wǎng)頁的行為層負責回答內(nèi)容應該如何對事件做出反應這一問題。他是指一種創(chuàng)建交互式網(wǎng)頁應用的網(wǎng)頁開發(fā)技術(shù)。 AngularJS。 優(yōu)點: 模板功能強大豐富,并且是聲明式的,自帶了豐富的Angular指令; 是一個比較完善的前端MV*框架,包含模板,數(shù)據(jù)雙向綁定,路由...
摘要:關(guān)于個人開源項目的一些總結(jié)項目地址項目簡介此項目名叫。網(wǎng)站目前實現(xiàn)了登錄注冊日歷導入文件考勤導出缺勤名單等核心功能。這對于小型項目來說并沒有什么問題。編譯后的大小關(guān)于文件上傳與導出功能文件上傳導出可以說是此項目最關(guān)鍵的點了。 關(guān)于個人開源項目(vue app)的一些總結(jié) 項目地址 https://github.com/BYChoo/record 項目簡介 此項目名叫:Record。是以...
摘要:關(guān)于個人開源項目的一些總結(jié)項目地址項目簡介此項目名叫。網(wǎng)站目前實現(xiàn)了登錄注冊日歷導入文件考勤導出缺勤名單等核心功能。這對于小型項目來說并沒有什么問題。編譯后的大小關(guān)于文件上傳與導出功能文件上傳導出可以說是此項目最關(guān)鍵的點了。 關(guān)于個人開源項目(vue app)的一些總結(jié) 項目地址 https://github.com/BYChoo/record 項目簡介 此項目名叫:Record。是以...
閱讀 1860·2021-09-23 11:21
閱讀 707·2019-08-30 15:55
閱讀 844·2019-08-29 15:40
閱讀 541·2019-08-29 12:56
閱讀 3175·2019-08-26 12:00
閱讀 3567·2019-08-23 18:24
閱讀 2259·2019-08-23 17:08
閱讀 1649·2019-08-23 17:03