摘要:和異步處理調(diào)用訪問數(shù)據(jù)采用的方式,這是一個(gè)異步過程,異步過程最基本的處理方式是事件或回調(diào),其實(shí)這兩種處理方式實(shí)現(xiàn)原理差不多,都需要在調(diào)用異步過程的時(shí)候傳入一個(gè)在異步過程結(jié)束的時(shí)候調(diào)用的接口。
Ajax 和異步處理
調(diào)用 API 訪問數(shù)據(jù)采用的 Ajax 方式,這是一個(gè)異步過程,異步過程最基本的處理方式是事件或回調(diào),其實(shí)這兩種處理方式實(shí)現(xiàn)原理差不多,都需要在調(diào)用異步過程的時(shí)候傳入一個(gè)在異步過程結(jié)束的時(shí)候調(diào)用的接口。比如 jQuery Ajax 的 success 就是典型的回調(diào)參數(shù)。不過使用 jQuery 處理異步推薦使用 Promise 處理方式。
Promise 處理方式也是通過注冊(cè)回調(diào)函數(shù)來完成的。jQuery 的 Promise 和 ES6 的標(biāo)準(zhǔn) Promise 有點(diǎn)不一樣,但在 then 上可以兼容,通常稱為 thenable。jQuery 的 Promise 沒有提供 .catch() 接口,但它自己定義的 .done()、.fail() 和 .always() 三個(gè)注冊(cè)回調(diào)的方式也很有特色,用起來很方便,它是在事件的方式來注冊(cè)的(即,可以注冊(cè)多個(gè)同類型的處理函數(shù),在該觸發(fā)的時(shí)候都會(huì)觸發(fā))。
當(dāng)然更直觀的一點(diǎn)的處理方式是使用 ES2017 帶來的 async/await 方式,可以用同步代碼的形式來寫異步代碼,當(dāng)然也有一些坑在里面。對(duì)于前端工程師來說,最大的坑就是有些瀏覽器不支持,需要進(jìn)行轉(zhuǎn)譯,所以如果前端代碼沒有構(gòu)建過程,一般還是就用 ES5 的語法兼容性好一些(jQuery 的 Promise 是支持 ES5 的,但是標(biāo)準(zhǔn) Promise 要 ES6 以后才可以使用)。
關(guān)于 JavaScript 異步處理相關(guān)的內(nèi)容可以參考
從小小題目逐步走進(jìn) JavaScript 異步調(diào)用
閑談異步調(diào)用“扁平”化
從地獄到天堂,Node 回調(diào)向 async/await 轉(zhuǎn)變
理解 JavaScript 的 async/await
從不用 try-catch 實(shí)現(xiàn)的 async/await 語法說錯(cuò)誤處理
自己封裝工具函數(shù)在處理 Ajax 的過程中,雖然有現(xiàn)成的庫(kù)(比如 jQuery.ajax,axios 等),它畢竟是為了通用目的設(shè)計(jì)的,在使用的時(shí)候仍然不免繁瑣。而在項(xiàng)目中,對(duì) Api 進(jìn)行調(diào)用的過程幾乎都大同小異。如果設(shè)計(jì)得當(dāng),就連錯(cuò)誤處理的方式都會(huì)是一樣的。因此,在項(xiàng)目?jī)?nèi)的 Ajax 調(diào)用其實(shí)可以進(jìn)行進(jìn)一步的封裝,使之在項(xiàng)目?jī)?nèi)使用起來更方便。如果接口方式發(fā)生變化,修改起來也更容易。
比如,當(dāng)前接口要求使用 POST 方法調(diào)用(暫不考慮 RESTful),參數(shù)必須包括 action,返回的數(shù)據(jù)以 JSON 方式提供,如果出錯(cuò),只要不是服務(wù)器異常都會(huì)返回特定的 JSON 數(shù)據(jù),包括一個(gè)不等于 0 的 code 和可選的 message 屬性。
那么用 jQuery 寫這么一個(gè) 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ù)器錯(cuò)誤"); });初步封裝
同一項(xiàng)目中,這樣的 Ajax 調(diào)用,基本上只有 data 部分和 .done 回調(diào)中的 else 部分不同,所以進(jìn)行一次封裝會(huì)大大減少代碼量,可以這樣封裝
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 或省略時(shí),表示沒有錯(cuò)誤, // 其它值表示錯(cuò)誤代碼 if (data.code) { if (data.message) { // 如果服務(wù)器返回了消息,那么向用戶呈現(xiàn)消息 // resolve(null),表示不需要后續(xù)進(jìn)行業(yè)務(wù)處理 alert(data.message); deffered.resolve(); } else { // 如果服務(wù)器沒返回消息,那么把 data 丟給外面的業(yè)務(wù)處理 deferred.reject(data); } } else { // 正常返回?cái)?shù)據(jù)的情況 deffered.resolve(data); } }) .fail(function() { // Ajax 調(diào)用失敗,向用戶呈現(xiàn)消息,同時(shí)不需要進(jìn)行后續(xù)的業(yè)務(wù)處理 alert("服務(wù)器錯(cuò)誤"); deffered.resolve(); }); return deferred.promise(); }
而業(yè)務(wù)層的調(diào)用就很簡(jiǎn)單了
appAjax("login", { username: "uname", password: "passwd" }).done(function(data) { if (data) { window.location.assign("home"); } }).fail(function() { alert("登錄失敗"); });更換 API 調(diào)用接口
上面的封裝對(duì)調(diào)用接口和返回?cái)?shù)據(jù)進(jìn)行了統(tǒng)一處理,把大部分項(xiàng)目接口約定的內(nèi)容都處理掉了,剩下在每次調(diào)用時(shí)需要處理的就是純粹的業(yè)務(wù)。
現(xiàn)在項(xiàng)目組決定不用 jQuery 的 Ajax,而是采用 axios 來調(diào)用 API(axios 不見得就比 jQuery 好,這里只是舉例),那么只需要修改一下 appAjax() 的實(shí)現(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 來實(shí)現(xiàn) Web Api 調(diào)用。但是為了保持原來的接口(jQuery Promise 對(duì)象有提供 .done()、.fail() 和 .always() 事件處理),appAjax 仍然不得不返回 jQuery Promise。這樣,即使所有地方都不再需要使用 jQuery,這里仍然得用。
去除 jQuery項(xiàng)目中應(yīng)該用還是不用 jQuery?請(qǐng)閱讀為什么要用原生 JavaScript 代替 jQuery?
就只在這里使用 jQuery 總讓人感覺如芒在背,想把它去掉。有兩個(gè)辦法
修改所有業(yè)務(wù)中的調(diào)用,去掉 .done()、.fail() 和 .always(),改成 .then()。這一步工作量較大,但基本無痛,因?yàn)?jQuery Promise 本身支持 .then()。但是有一點(diǎn)需要特別注意,這一點(diǎn)稍后說明
自己寫個(gè)適配器,兼容 jQuery Promise 的接口,工作量也不小,但關(guān)鍵是要充分測(cè)試,避免差錯(cuò)。
上面提到第 1 種方法中有一點(diǎn)需要特別注意,那就是 .then() 和 .done() 系列函數(shù)在處理方式上有所不同。.then() 是按 Promise 的特性設(shè)計(jì)的,它返回的是另一個(gè) Promise 對(duì)象;而 .done() 系列函數(shù)是按事件機(jī)制實(shí)現(xiàn)的,返回的是原來的 Promise 對(duì)象。所以像下面這樣的代碼在修改時(shí)就要注意了
appAjax(url, params) .done(function(data) { console.log("第 1 處處理", data) }) .done(function(data) { console.log("第 2 處處理", data) }); // 第 1 處處理 {} // 第 2 處處理 {}
簡(jiǎn)單的把 .done() 改成 .then() 之后(注意不需要使用 Bluebird,因?yàn)?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)講了,這里正確的處理方式是合并多個(gè) 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è)計(jì)
我們的 appAjax() 接口部分也可以設(shè)計(jì)成 Promise 實(shí)現(xiàn),這是一個(gè)更通用的接口。既使用不用 ES2015+ 特性,也可以使用像 jQuery Promise 或 Bluebird 這樣的三方庫(kù)提供的 Promise。
function appAjax(action, params) { // axios 依賴于 Promise,ES5 中可以使用 Bluebird 提供的 Promise return axios .post(apiUrl, { data: $.extend({ action: action }, params) }) .then(function(data) { // 這里調(diào)整了判斷順序,會(huì)讓代碼看起來更簡(jiǎn)潔 if (!data.code) { return data; } if (!data.message) { throw data; } alert(data.message); }, function() { alert("服務(wù)器錯(cuò)誤"); }); }
不過現(xiàn)在前端有構(gòu)建工具,可以使用 ES2015+ 配置 Babel,也可以使用 TypeScript …… 總之,選擇很多,寫起來也很方便。那么在設(shè)計(jì)的時(shí)候就不用局限于 ES5 所支持的內(nèi)容了。所以可以考慮用 Promise + async/await 來實(shí)現(xiàn)
async function appAjax(action, params) { // axios 依賴于 Promise,ES5 中可以使用 Bluebird 提供的 Promise const data = await axios .post(apiUrl, { data: $.extend({ action: action }, params) }) // 這里模擬一個(gè)包含錯(cuò)誤消息的結(jié)果,以便后面統(tǒng)一處理錯(cuò)誤 // 這樣就不需要用 try ... catch 了 .catch(() => ({ code: -1, message: "服務(wù)器錯(cuò)誤" })); if (!data.code) { return data; } if (!data.message) { throw data; } alert(data.message); }
上面代碼中使用 .catch() 來避免 try ... catch ... 的技巧在從不用 try-catch 實(shí)現(xiàn)的 async/await 語法說錯(cuò)誤處理中提到過。
當(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"); }
對(duì)于多次 .done() 的改造:
const data = await appAjax(url, params); console.log("第 1 處處理", data); console.log("第 2 處處理", data);小結(jié)
本文以封裝 Ajax 調(diào)用為例,看似在講述異步調(diào)用。但實(shí)際想告訴大家的東西是:如何將一個(gè)常用的功能封裝起來,實(shí)現(xiàn)代碼重用和更簡(jiǎn)潔的調(diào)用;以及在封裝的過程中需要考慮的問題——向前和向后的兼容性,在做工具函數(shù)封裝的時(shí)候,應(yīng)該盡量避免和某個(gè)特定的工具特性綁定,向公共標(biāo)準(zhǔn)靠攏——不知大家是否有所體會(huì)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/89850.html
摘要:開公眾號(hào)差不多兩年了,有不少原創(chuàng)教程,當(dāng)原創(chuàng)越來越多時(shí),大家搜索起來就很不方便,因此做了一個(gè)索引幫助大家快速找到需要的文章系列處理登錄請(qǐng)求前后端分離一使用完美處理權(quán)限問題前后端分離二使用完美處理權(quán)限問題前后端分離三中密碼加鹽與中異常統(tǒng)一處理 開公眾號(hào)差不多兩年了,有不少原創(chuàng)教程,當(dāng)原創(chuàng)越來越多時(shí),大家搜索起來就很不方便,因此做了一個(gè)索引幫助大家快速找到需要的文章! Spring Boo...
摘要:更新嘗試了一下實(shí)現(xiàn)前后端分離,新的文章如下前后端分離之初試更新可另外用實(shí)現(xiàn)前后端分離,這篇文章可能局限性太大,只是個(gè)人的入門實(shí)踐剛剛學(xué)習(xí)前端快一年,后臺(tái)方面了解甚少,于是決定踩踩坑,學(xué)習(xí)一下。 2018.9.6更新:嘗試了一下REST framework實(shí)現(xiàn)前后端分離,新的文章如下Django前后端分離之REST framework初試 2018.8.27更新:可另外用 restful...
摘要:刪除后指定產(chǎn)品不存在獲取商品列表未分頁獲取全部商品成功系列的表殼材料為輕巧的銀色及深空灰色陽極氧化鋁金屬,強(qiáng)化玻璃材質(zhì)為顯示屏提供保護(hù)。外觀設(shè)計(jì)不再棱角分明,表層玻璃邊有一個(gè)弧度向下延伸,與陽極氧化鋁金屬機(jī)身邊框銜接。 背景 API 就是開發(fā)者使用的界面。我的目標(biāo)不僅是能用,而且好用,跨平臺(tái)(PC, Android, IOS, etc...)使用。本文將詳細(xì)介紹 API 的設(shè)計(jì)及異常處...
閱讀 1189·2021-11-24 09:39
閱讀 2891·2021-09-26 09:55
閱讀 18494·2021-08-23 09:47
閱讀 3746·2019-08-30 15:52
閱讀 997·2019-08-29 13:49
閱讀 1161·2019-08-23 18:00
閱讀 992·2019-08-23 16:42
閱讀 1832·2019-08-23 14:28