- 原文做者:Kaloyan Kosev
- 原文連接:https://css-tricks.com/adapting-javascript-abstractions-time/
- 翻譯譯者:小溪裏
- 校對:華翔、小冬
即便尚未讀過個人文章《在處理網絡數據的 JavaScript 抽象的重要性》,你也頗有可能已經意識到代碼的可維護性和可擴展性很重要,這也是介紹 JavaScript
抽象的目的。javascript
爲了更加清楚的說明,咱們假設在 JavaScript
中抽象是一個模塊。css
一個模塊的最初實現只是它們漫長(也許是持久的)的生命週期過程的開始。我將一個模塊的生命週期分紅 3 個重要階段。前端
在我先前的文章中,重心放在了第一點上。而在這篇文章中,我將把重點放在第二點上。java
模塊更改是我常常碰到的一個難題。與引入模塊相比,開發者維護和更改模塊的方式對保證項目的可維護性和可拓展性是同等重要甚至是更加劇要。我看過一個寫得很好、抽象得很好的模塊隨着時間推移歷經屢次更改後被完全毀了。我本身也常常是形成那種破壞性更改的其中一個。ios
當我說破壞性,我指的是對可維護性和可擴展性方面的破壞。我也明白,當面臨項目最後交付期限的壓力時,放慢速度以進行更好的修改設計並非優先選擇。git
開發者作出非最優修改的緣由可能有不少種,我在這裏想特別強調一個:github
這種方法讓你的修改顯得更專業。json
讓咱們從一個 API
模塊的代碼示例開始。之因此選擇這個示例,是由於與外部 API
通訊是我在開始項目時定義的最基本的抽象之一。這裏的想法是將全部與 API
相關的配置和設置(如基本 URL
,錯誤處理邏輯等)存儲在這個模塊中.axios
我將編寫一個設置 API.url
、一個私有方法 API._handleError()
和一個公共方法 API.get()
:小程序
class API { constructor() { this.url = 'http://whatever.api/v1/'; } /** * API 數據獲取的特有方法 * 檢查一個 HTTP 返回的狀態碼是否在成功的範圍內 */ _handleError(_res) { return _res.ok ? _res : Promise.reject(_res.statusText); } /** * 獲取數據 * @return {Promise} */ get(_endpoint) { return window.fetch(this.url + _endpoint, { method: 'GET' }) .then(this._handleError) .then( res => res.json()) .catch( error => { alert('So sad. There was an error.'); throw new Error(error); }); } };
在這個模塊中,公共方法 API.get()
返回一個 Promise
。咱們使用咱們抽象出來的 API
模塊,而不是經過 window.fetch()
直接調用 Fetch API
。例如,獲取用戶信息 API.get('user')
或當前天氣預報 API.get('weather')
。實現這個功能的重要意義在於Fetch API與咱們的代碼沒有緊密耦合。
如今,咱們面臨一個修改!技術主管要求咱們把獲取遠程數據的方式切換到Axios上。咱們該如何應對呢?
在咱們開始討論方法以前,咱們先來總結一下什麼是不變的,什麼是須要修改的:
更改:在公共 API.get()
方法中
axios()
的 window.fetch()
調用;須要再次返回一個 Promise
, 以保持接口的一致, 好在 Axios
是基於 Promise
的,太棒了!JSON
。經過 Fetch API
並經過鏈式調用 .then( res => res.json())
語句來解析響應的數據。使用 Axios
,服務器響應是在 data
屬性中,咱們不須要解析它。所以,咱們須要將.then
語句改成.then(res => res.data)
。更改:在私有 API._handleError
方法中:
ok
布爾標誌,可是,還有 statusText
屬性。咱們能夠經過它來串起來,若是它的值是 OK
,那麼一切將沒什麼問題(附註:在 Fetch API
中 OK
爲 true
與在 Axios
中的 statusText
爲 OK
是不同的。但爲了便於理解,爲了避免過於寬泛,再也不引入任何高級錯誤處理。)API.url
保持不變,咱們會發現錯誤並以愉快的方式提醒他們。講解完畢!如今讓咱們深刻應用這些修改的實際方法。
class API { constructor() { this.url = 'http://whatever.api/v1/'; // 如出一轍的 } _handleError(_res) { // DELETE: return _res.ok ? _res : Promise.reject(_res.statusText); return _res.statusText === 'OK' ? _res : Promise.reject(_res.statusText); } get(_endpoint) { // DELETE: return window.fetch(this.url + _endpoint, { method: 'GET' }) return axios.get(this.url + _endpoint) .then(this._handleError) // DELETE: .then( res => res.json()) .then( res => res.data) .catch( error => { alert('So sad. There was an error.'); throw new Error(error); }); } };
聽起來很合理。 提交、上傳、合併、完成。
不過,在某些狀況下,這可能不是一個好主意。想象如下情景:在切換到 Axios
以後,你會發現有一個功能並不適用於 XMLHttpRequests( Axios
的獲取數據的方法),但以前使用 Fetch API
的新型瀏覽器工做得很好。咱們如今該怎麼辦?
咱們的技術負責人說,讓咱們使用舊的 API
實現這個特定的用例,並繼續在其餘地方使用 Axios
。你該作什麼?在源代碼管理歷史記錄中找到舊的 API
模塊。還原。在這裏和那裏添加 if
語句。這樣聽起來並不太友好。
必須有一個更容易,更易於維護和可擴展的方式來進行更改!那麼,下面的就是。
重構的需求立刻來了!讓咱們從新開始,咱們再也不刪除代碼,而是讓咱們在另外一個抽象中移動 Fetch
的特定邏輯,這將做爲全部 Fetch
特定的適配器(或包裝器)。
HEY!???對於那些熟悉適配器模式(也被稱爲包裝模式)的人來講,是的,那正是咱們前進的方向!若是您對全部的細節感興趣,請參閱這裏個人介紹。
以下所示:
將跟 Fetch
相關的幾行代碼拿出來,單獨抽象爲一個新的方法 FetchAdapter
。
class FetchAdapter { _handleError(_res) { return _res.ok ? _res : Promise.reject(_res.statusText); } get(_endpoint) { return window.fetch(_endpoint, { method: 'GET' }) .then(this._handleError) .then( res => res.json()); } };
重構API模塊,刪除 Fetch
相關代碼,其他代碼保持不變。添加 FetchAdapter
做爲依賴(以某種方式):
class API { constructor(_adapter = new FetchAdapter()) { this.adapter = _adapter; this.url = 'http://whatever.api/v1/'; } get(_endpoint) { return this.adapter.get(_endpoint) .catch( error => { alert('So sad. There was an error.'); throw new Error(error); }); } };
如今狀況不同了!這種結構能讓你處理各類不一樣的獲取數據的場景(適配器)改。最後一步,你猜對了!寫一個 AxiosAdapter
!
const AxiosAdapter = { _handleError(_res) { return _res.statusText === 'OK' ? _res : Promise.reject(_res.statusText); }, get(_endpoint) { return axios.get(_endpoint) then(this._handleError) .then( res => res.data); } };
在 API
模塊中,將默認適配器改成 AxiosAdapter
:
class API { constructor(_adapter = new /*FetchAdapter()*/ AxiosAdapter()) { this.adapter = _adapter; /* ... */ } /* ... */ };
真棒!若是咱們須要在這個特定的用例中使用舊的 API
實現,而且在其餘地方繼續使用Axios
?沒問題!
//無論你喜歡與否,將其導入你的模塊,由於這只是一個例子。 import API from './API'; import FetchAdapter from './FetchAdapter'; //使用 AxiosAdapter(默認的) const API = new API(); API.get('user'); // 使用FetchAdapter const legacyAPI = new API(new FetchAdapter()); legacyAPI.get('user');
因此下次你須要改變你的項目時,評估下面哪一種方法更有意義:
總結請根據你的場景選擇性使用。若是你的代碼庫濫用適配器和引入太多的抽象可能會致使複雜性增長,這也是很差的。
愉快的去使用適配器吧!
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。
iKcamp官網:https://www.ikcamp.com
訪問官網更快閱讀所有免費分享課程:
《iKcamp出品|全網最新|微信小程序|基於最新版1.0開發者工具之初中級培訓教程分享》
《iKcamp出品|基於Koa2搭建Node.js實戰項目教程》
包含:文章、視頻、源代碼