譯|調整JavaScript抽象的迭代方案

即便尚未讀過個人文章《在處理網絡數據的 JavaScript 抽象的重要性》,你也頗有可能已經意識到代碼的可維護性和可擴展性很重要,這也是介紹 JavaScript 抽象的目的。javascript

爲了更加清楚的說明,咱們假設在 JavaScript 中抽象是一個模塊。css

一個模塊的最初實現只是它們漫長(也許是持久的)的生命週期過程的開始。我將一個模塊的生命週期分紅 3 個重要階段。前端

  1. 引入模塊。在項目中編寫該模塊或複用該模塊;
  2. 調整模塊。隨時調整模塊;
  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上。咱們該如何應對呢?

在咱們開始討論方法以前,咱們先來總結一下什麼是不變的,什麼是須要修改的:

  1. 更改:在公共 API.get() 方法中

    • 須要修改 axios()window.fetch()調用;須要再次返回一個 Promise, 以保持接口的一致, 好在 Axios 是基於 Promise 的,太棒了!
    • 服務器的響應的是 JSON。經過 Fetch API 並經過鏈式調用 .then( res => res.json()) 語句來解析響應的數據。使用 Axios,服務器響應是在 data 屬性中,咱們不須要解析它。所以,咱們須要將.then語句改成.then(res => res.data)
  2. 更改:在私有 API._handleError 方法中:

    • 在響應對象中缺乏 ok 布爾標誌,可是,還有 statusText 屬性。咱們能夠經過它來串起來,若是它的值是 OK,那麼一切將沒什麼問題(附註:在 Fetch APIOKtrue 與在 Axios 中的 statusTextOK 是不同的。但爲了便於理解,爲了避免過於寬泛,再也不引入任何高級錯誤處理。)
  3. 不變之處: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 以後,你會發現有一個功能並不適用於 XMLHttpRequestsAxios 的獲取數據的方法),但以前使用 Fetch API 的新型瀏覽器工做得很好。咱們如今該怎麼辦?

咱們的技術負責人說,讓咱們使用舊的 API 實現這個特定的用例,並繼續在其餘地方使用 Axios 。你該作什麼?在源代碼管理歷史記錄中找到舊的 API 模塊。還原。在這裏和那裏添加 if 語句。這樣聽起來並不太友好。

必須有一個更容易,更易於維護和可擴展的方式來進行更改!那麼,下面的就是。

方法二:重構代碼,作適配!

重構的需求立刻來了!讓咱們從新開始,咱們再也不刪除代碼,而是讓咱們在另外一個抽象中移動 Fetch 的特定邏輯,這將做爲全部 Fetch 特定的適配器(或包裝器)。

HEY!???對於那些熟悉適配器模式(也被稱爲包裝模式)的人來講,是的,那正是咱們前進的方向!若是您對全部的細節感興趣,請參閱這裏個人介紹。

以下所示:

步驟1

將跟 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());
  }
};

步驟2

重構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實戰項目教程》
包含:文章、視頻、源代碼

相關文章
相關標籤/搜索