前后分離模型之封裝 Api 調(diào)用

Ajax 和異步處理

調(diào)用 API 訪問數(shù)據(jù)采用的 Ajax 方式闸盔,這是一個異步過程,異步過程最基本的處理方式是事件或回調(diào)琳省,其實這兩種處理方式實現(xiàn)原理差不多迎吵,都需要在調(diào)用異步過程的時候傳入一個在異步過程結(jié)束的時候調(diào)用的接口。比如 jQuery Ajax 的 success 就是典型的回調(diào)參數(shù)针贬。不過使用 jQuery 處理異步推薦使用 Promise 處理方式击费。

Promise 處理方式也是通過注冊回調(diào)函數(shù)來完成的。jQuery 的 Promise 和 ES6 的標(biāo)準(zhǔn) Promise 有點不一樣桦他,但在 then 上可以兼容蔫巩,通常稱為 thenable。jQuery 的 Promise 沒有提供 .catch() 接口快压,但它自己定義的 .done()圆仔、.fail().always() 三個注冊回調(diào)的方式也很有特色,用起來很方便蔫劣,它是在事件的方式來注冊的(即坪郭,可以注冊多個同類型的處理函數(shù),在該觸發(fā)的時候都會觸發(fā))拦宣。

當(dāng)然更直觀的一點的處理方式是使用 ES2017 帶來的 async/await 方式截粗,可以用同步代碼的形式來寫異步代碼,當(dāng)然也有一些坑在里面鸵隧。對于前端工程師來說绸罗,最大的坑就是有些瀏覽器不支持,需要進行轉(zhuǎn)譯豆瘫,所以如果前端代碼沒有構(gòu)建過程珊蟀,一般還是就用 ES5 的語法兼容性好一些(jQuery 的 Promise 是支持 ES5 的,但是標(biāo)準(zhǔn) Promise 要 ES6 以后才可以使用)外驱。

關(guān)于 JavaScript 異步處理相關(guān)的內(nèi)容可以參考

自己封裝工具函數(shù)

在處理 Ajax 的過程中,雖然有現(xiàn)成的庫(比如 jQuery.ajax昵宇,axios 等)磅崭,它畢竟是為了通用目的設(shè)計的,在使用的時候仍然不免繁瑣瓦哎。而在項目中砸喻,對 Api 進行調(diào)用的過程幾乎都大同小異柔逼。如果設(shè)計得當(dāng),就連錯誤處理的方式都會是一樣的割岛。因此愉适,在項目內(nèi)的 Ajax 調(diào)用其實可以進行進一步的封裝,使之在項目內(nèi)使用起來更方便癣漆。如果接口方式發(fā)生變化维咸,修改起來也更容易。

比如惠爽,當(dāng)前接口要求使用 POST 方法調(diào)用(暫不考慮 RESTful)癌蓖,參數(shù)必須包括 action,返回的數(shù)據(jù)以 JSON 方式提供疆股,如果出錯费坊,只要不是服務(wù)器異常都會返回特定的 JSON 數(shù)據(jù),包括一個不等于 0 的 code 和可選的 message 屬性旬痹。

那么用 jQuery 寫這么一個 Ajax 調(diào)用附井,大概是這樣

const apiUrl = "http://api.some.com/";

jQuery
    .ajax(url, {
        type: "post",
        dataType: "json",
        data: {
            action: "login",
            username: "uname",
            password: "passwd"
        }
    })
    .done(function(data) {
        if (data.code) {
            alert(data.message || "登錄失敗两残!");
        } else {
            window.location.assign("home");
        }
    })
    .fail(function() {
        alert("服務(wù)器錯誤");
    });

初步封裝

同一項目中永毅,這樣的 Ajax 調(diào)用,基本上只有 data 部分和 .done 回調(diào)中的 else 部分不同人弓,所以進行一次封裝會大大減少代碼量沼死,可以這樣封裝

function appAjax(action, params) {
    var deffered = $.Deferred();

    jQuery
        .ajax(apiUrl, {
            type: "post",
            dataType: "json",
            data: $.extend({
                action: action
            }, params)
        })
        .done(function(data) {
            // 當(dāng) code 為 0 或省略時,表示沒有錯誤崔赌,
            // 其它值表示錯誤代碼
            if (data.code) {
                if (data.message) {
                    // 如果服務(wù)器返回了消息意蛀,那么向用戶呈現(xiàn)消息
                    // resolve(null),表示不需要后續(xù)進行業(yè)務(wù)處理
                    alert(data.message);
                    deffered.resolve();
                } else {
                    // 如果服務(wù)器沒返回消息健芭,那么把 data 丟給外面的業(yè)務(wù)處理
                    deferred.reject(data);
                }
            } else {
                // 正常返回數(shù)據(jù)的情況
                deffered.resolve(data);
            }
        })
        .fail(function() {
            // Ajax 調(diào)用失敗县钥,向用戶呈現(xiàn)消息,同時不需要進行后續(xù)的業(yè)務(wù)處理
            alert("服務(wù)器錯誤");
            deffered.resolve();
        });

    return deferred.promise();
}

而業(yè)務(wù)層的調(diào)用就很簡單了

appAjax("login", {
    username: "uname",
    password: "passwd"
}).done(function(data) {
    if (data) {
        window.location.assign("home");
    }
}).fail(function() {
    alert("登錄失敗");
});

更換 API 調(diào)用接口

上面的封裝對調(diào)用接口和返回數(shù)據(jù)進行了統(tǒng)一處理慈迈,把大部分項目接口約定的內(nèi)容都處理掉了若贮,剩下在每次調(diào)用時需要處理的就是純粹的業(yè)務(wù)。

現(xiàn)在項目組決定不用 jQuery 的 Ajax痒留,而是采用 axios 來調(diào)用 API(axios 不見得就比 jQuery 好谴麦,這里只是舉例),那么只需要修改一下 appAjax() 的實現(xiàn)即可伸头。所有業(yè)務(wù)調(diào)用都不需要修改匾效。

假設(shè)現(xiàn)在的目標(biāo)環(huán)境仍然是 ES5,那么需要第三方 Promise 提供恤磷,這里擬用 Bluebird面哼,兼容原生 Promise 接口(在 HTML 中引入雪侥,未直接出現(xiàn)在 JS 代碼中)。

function appAjax(action, params) {
    var deffered = $.Deferred();

    axios
        .post(apiUrl, {
            data: $.extend({
                action: action
            }, params)
        })
        .then(function(data) { ... }, function() { ... });

    return deferred.promise();
}

這次的封裝采用了 axios 來實現(xiàn) Web Api 調(diào)用精绎。但是為了保持原來的接口(jQuery Promise 對象有提供 .done().fail().always() 事件處理)锌妻,appAjax 仍然不得不返回 jQuery Promise代乃。這樣,即使所有地方都不再需要使用 jQuery仿粹,這里仍然得用搁吓。

項目中應(yīng)該用還是不用 jQuery?請閱讀為什么要用原生 JavaScript 代替 jQuery吭历?

去除 jQuery

就只在這里使用 jQuery 總讓人感覺如芒在背堕仔,想把它去掉。有兩個辦法

  1. 修改所有業(yè)務(wù)中的調(diào)用晌区,去掉 .done()摩骨、.fail().always(),改成 .then()朗若。這一步工作量較大恼五,但基本無痛,因為 jQuery Promise 本身支持 .then()哭懈。但是有一點需要特別注意灾馒,這一點稍后說明
  2. 自己寫個適配器,兼容 jQuery Promise 的接口遣总,工作量也不小睬罗,但關(guān)鍵是要充分測試,避免差錯旭斥。

上面提到第 1 種方法中有一點需要特別注意容达,那就是 .then().done() 系列函數(shù)在處理方式上有所不同。.then() 是按 Promise 的特性設(shè)計的琉预,它返回的是另一個 Promise 對象董饰;而 .done() 系列函數(shù)是按事件機制實現(xiàn)的,返回的是原來的 Promise 對象圆米。所以像下面這樣的代碼在修改時就要注意了

appAjax(url, params)
    .done(function(data) { console.log("第 1 處處理", data) })
    .done(function(data) { console.log("第 2 處處理", data) });
// 第 1 處處理 {}
// 第 2 處處理 {}

簡單的把 .done() 改成 .then() 之后(注意不需要使用 Bluebird卒暂,因為 jQuery Promise 支持 .then()

appAjax(url, params)
    .then(function(data) { console.log("第 1 處處理", data); })
    .then(function(data) { console.log("第 2 處處理", data); });
// 第 1 處處理 {}
// 第 2 處處理 undefined

原因上面已經(jīng)講了,這里正確的處理方式是合并多個 done 的代碼娄帖,或者在 .then() 處理函數(shù)中返回 data

appAjax(url, params)
    .then(function(data) {
        console.log("第 1 處處理", data);
        return data;
    })
    .then(function(data) {
        console.log("第 2 處處理", data);
    });

使用 Promise 接口改善設(shè)計

我們的 appAjax() 接口部分也可以設(shè)計成 Promise 實現(xiàn)也祠,這是一個更通用的接口。既使用不用 ES2015+ 特性近速,也可以使用像 jQuery Promise 或 Bluebird 這樣的三方庫提供的 Promise诈嘿。

function appAjax(action, params) {
    // axios 依賴于 Promise堪旧,ES5 中可以使用 Bluebird 提供的 Promise
    return axios
        .post(apiUrl, {
            data: $.extend({
                action: action
            }, params)
        })
        .then(function(data) {
            // 這里調(diào)整了判斷順序,會讓代碼看起來更簡潔
            if (!data.code) { return data; }
            if (!data.message) { throw data; }
            alert(data.message);
        }, function() {
            alert("服務(wù)器錯誤");
        });
}

不過現(xiàn)在前端有構(gòu)建工具奖亚,可以使用 ES2015+ 配置 Babel淳梦,也可以使用 TypeScript …… 總之,選擇很多昔字,寫起來也很方便爆袍。那么在設(shè)計的時候就不用局限于 ES5 所支持的內(nèi)容了。所以可以考慮用 Promise + async/await 來實現(xiàn)

async function appAjax(action, params) {
    // axios 依賴于 Promise作郭,ES5 中可以使用 Bluebird 提供的 Promise
    const data = await axios
        .post(apiUrl, {
            data: $.extend({
                action: action
            }, params)
        })
        // 這里模擬一個包含錯誤消息的結(jié)果陨囊,以便后面統(tǒng)一處理錯誤
        // 這樣就不需要用 try ... catch 了
        .catch(() => ({ code: -1, message: "服務(wù)器錯誤" }));

    if (!data.code) { return data; }
    if (!data.message) { throw data; }

    alert(data.message);
}

上面代碼中使用 .catch() 來避免 try ... catch ... 的技巧在從不用 try-catch 實現(xiàn)的 async/await 語法說錯誤處理中提到過。

當(dāng)然業(yè)務(wù)層調(diào)用也可以使用 async/await(記得寫在 async 函數(shù)中):

const data = await appAjax("login", {
    username: "uname",
    password: "passwd"
}).catch(() => {
    alert("登錄失敗");
});

if (data) {
    window.location.assign("home");
}

對于多次 .done() 的改造:

const data = await appAjax(url, params);
console.log("第 1 處處理", data);
console.log("第 2 處處理", data);

小結(jié)

本文以封裝 Ajax 調(diào)用為例夹攒,看似在講述異步調(diào)用蜘醋。但實際想告訴大家的東西是:如何將一個常用的功能封裝起來,實現(xiàn)代碼重用和更簡潔的調(diào)用咏尝;以及在封裝的過程中需要考慮的問題——向前和向后的兼容性压语,在做工具函數(shù)封裝的時候,應(yīng)該盡量避免和某個特定的工具特性綁定状土,向公共標(biāo)準(zhǔn)靠攏——不知大家是否有所體會无蜂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蒙谓,隨后出現(xiàn)的幾起案子斥季,更是在濱河造成了極大的恐慌,老刑警劉巖累驮,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酣倾,死亡現(xiàn)場離奇詭異,居然都是意外死亡谤专,警方通過查閱死者的電腦和手機躁锡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來置侍,“玉大人映之,你說我怎么就攤上這事±唬” “怎么了杠输?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秕衙。 經(jīng)常有香客問我蠢甲,道長,這世上最難降的妖魔是什么据忘? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任鹦牛,我火速辦了婚禮搞糕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘曼追。我一直安慰自己窍仰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布礼殊。 她就那樣靜靜地躺著辈赋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪膏燕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天悟民,我揣著相機與錄音坝辫,去河邊找鬼。 笑死射亏,一個胖子當(dāng)著我的面吹牛近忙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播智润,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼及舍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窟绷?” 一聲冷哼從身側(cè)響起锯玛,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兼蜈,沒想到半個月后攘残,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡为狸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年歼郭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辐棒。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡病曾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漾根,到底是詐尸還是另有隱情泰涂,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布立叛,位于F島的核電站负敏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秘蛇。R本人自食惡果不足惜其做,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一顶考、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妖泄,春花似錦驹沿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至罚渐,卻和暖如春却汉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荷并。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工合砂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人源织。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓翩伪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谈息。 傳聞我的和親對象是個殘疾皇子缘屹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

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

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的侠仇,如果沒有異步編程轻姿,根本...
    呼呼哥閱讀 7,311評論 5 22
  • Promiese 簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果逻炊,語法上說踢代,Pr...
    雨飛飛雨閱讀 3,358評論 0 19
  • 00胳挎、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大溺森。它由社區(qū)...
    夜幕小草閱讀 2,133評論 0 12
  • 單線程與異步 Javascript是單線程運行慕爬、支持異步機制的語言。進入正題之前屏积,我們有必要先理解這種運行方式医窿。 ...
    貝聊科技閱讀 626評論 0 0
  • 被英語逼瘋的這幾天,痛定思痛炊林,各種挖掘?qū)W英語的方法姥卢,網(wǎng)絡(luò)視頻書籍,各方各面在尋找學(xué)英語的資料、書籍独榴,幾乎到了“窮兇...
    695e0145188a閱讀 822評論 0 5