JavaScript >> Javascript 文檔 >  >> Tags >> API

如何使用 JavaScript 類和 Fetch 編寫 API 包裝器

如何使用 JavaScript 類編寫 API 包裝器,通過 Fetch 使用方便、易於記憶的方法調用 JSON 佔位符 API。

開始使用

在本教程中,我們將使用 CheatCode 的全棧 JavaScript 框架 Joystick。 Joystick 將前端 UI 框架與用於構建應用的 Node.js 後端結合在一起。

首先,我們要通過 NPM 安裝 Joystick。確保在安裝之前使用 Node.js 16+ 以確保兼容性(如果您需要學習如何安裝 Node.js 或在計算機上運行多個版本,請先閱讀本教程):

終端

npm i -g @joystick.js/cli

這將在您的計算機上全局安裝操縱桿。安裝好之後,接下來我們新建一個項目:

終端

joystick create app

幾秒鐘後,您將看到一條消息已註銷到 02 進入你的新項目並運行 17

終端

cd app && joystick start

在此之後,您的應用應該可以運行了,我們可以開始了。

編寫 API 包裝類

在本教程中,我們將為 JSON Placeholder API 編寫一個包裝器,這是一個用於測試的免費 HTTP REST API。我們的目標是創建一個可重用的“包裝器”,幫助我們簡化向 API 發出請求的過程。

首先,我們將構建 API 包裝器本身作為一個 JavaScript 類。這將為我們提供一種方法——如果我們願意的話——創建包裝器的多個實例。在我們剛剛創建的應用程序中,讓我們打開 25 項目根目錄下的文件夾,並在 34 處創建一個新文件 :

/api/jsonplaceholder/index.js

class JSONPlaceholder {
  constructor() {
    this.endpoints = {};
  }
}

export default new JSONPlaceholder();

為我們的包裝器創建一個骨架,在這裡,我們使用 43 設置一個基本的 JavaScript 類 函數——在 57 之後立即調用的函數 在 JavaScript 類上調用關鍵字——在類 66 上設置一個空對象 .在內部,隨著我們的進展,我們將構建這個 71 包含用於動態生成我們希望包裝器執行的 HTTP 請求的方法(定義在對像上的函數)的對象。

在我們文件的底部,雖然技術上我們可以只導出類本身(沒有 85 關鍵字),在這裡,為了測試,我們將創建一個實例並將其導出為 99 .這將允許我們直接從應用程序的其他地方導入和調用我們的包裝器,而無需先執行以下操作:

import JSONPlaceholder from 'api/jsonplaceholder/index.js';

const jsonPlaceholder = new JSONPlaceholder();

jsonPlaceholder.posts('list');

相反,我們只能這樣做:

import jsonPlaceholder from './api/jsonplaceholder/index.js';

jsonPlaceholder.posts('list');

為了了解我們是如何做到這一點的,接下來,讓我們構建 101 構造函數中的對象,並解釋它將如何幫助我們執行請求。

/api/jsonplaceholder/index.js

import fetch from 'node-fetch';

class JSONPlaceholder {
  constructor() {
    this.endpoints = {
      posts: {
        list: (options = {}) => {
          return {
            method: 'GET',
            resource: `/posts${options.postId ? `/${options.postId}` : ''}`,
            params: {},
            body: null,
          };
        },
      },
    };
  }
}

export default new JSONPlaceholder();

當我們完成包裝器時,我們的目標是能夠像這樣調用 API 端點:110 並接收來自 JSON Placeholder API 的響應,而無需執行任何額外步驟。

為此,我們需要一種標準化的方式來生成我們將要執行的 HTTP 請求。這就是我們上面所做的。我們知道,為了執行對 API 的請求,我們可能需要四件事:

  1. 目標端點支持的HTTP方法(即122 , 133 , 146 , 或 150 )。
  2. 端點的資源或 URL。
  3. 任何可選或必需的查詢參數。
  4. 可選或必需的 HTTP 正文對象。

在這裡,我們創建一個模板來指定這四件事。為了讓我們的包裝器井井有條,在我們的 160 對象,我們創建另一個屬性 172 它表示我們要為其生成請求模板的 API 資源。嵌套在此之下,我們將函數分配給具有描述 HTTP 請求正在做什麼的名稱的屬性,返回與該任務相關的模板。

在上面的示例中,我們想要返回一個帖子列表。為此,我們需要創建一個模板告訴我們執行一個 HTTP 184193 的請求 JSON 佔位符 API 中的 URL。在有條件的情況下,我們也需要能夠將帖子的 ID 傳遞到此端點,例如 208211 .

這就是我們將請求模板生成器定義為函數的原因。這允許我們——如果需要的話——接受調用包裝器時傳遞的一組選項(例如,在這裡,我們想要接受我們期望通過 225 )。

作為我們函數的返回,我們返回一個對象,然後我們可以在以後的代碼中使用它來執行實際的 HTTP 請求。真快,讓我們構建其餘的請求模板生成器:

/api/jsonplaceholder/index.js

class JSONPlaceholder {
  constructor() {
    this.endpoints = {
      posts: {
        create: (options = {}) => {
          return {
            method: 'POST',
            resource: `/posts`,
            params: {},
            body: {
              ...options,
            },
          };
        },
        list: (options = {}) => {
          return {
            method: 'GET',
            resource: `/posts${options.postId ? `/${options.postId}` : ''}`,
            params: {},
            body: null,
          };
        },
        post: (options = {}) => {
          if (!options.postId) {
            throw new Error('A postId is required for the posts.post method.');
          }

          return {
            method: 'GET',
            resource: `/posts/${options.postId}`,
            params: {},
            body: null,
          };
        },
        comments: (options = {}) => {
          if (!options.postId) {
            throw new Error('A postId is required for the posts.comments method.');
          }

          return {
            method: 'GET',
            resource: `/posts/${options.postId}/comments`,
            params: {},
            body: null,
          };
        },
      },
    };
  }
}

export default new JSONPlaceholder();

重複相同的模式,只是為了不同的端點和不同的目的。對於我們想要支持的每個端點,在 239 下 對象,我們添加一個分配給方便名稱的函數,接受一組可能的 243 並將請求模板作為具有四個屬性的對象返回:251 , 269 , 276 , 和 287 .

密切注意模板如何根據端點而變化。有些使用不同的 297 s 而其他人有一個 307 而其他人則沒有。這就是我們所說的擁有標準化模板的意思。它們都返回具有相同形狀的對象,但是,它們設置的 on 該對像根據我們嘗試訪問的端點的要求而有所不同。

我們還應該注意318 模板和 325 模板。在這裡,如果 334 我們會拋出錯誤 未定義為需要帖子 ID 才能滿足這些端點的要求。

接下來,我們需要使用這些對象。請記住,我們的目標是達到可以調用 345 的程度 在我們的代碼中並返回一個帖子列表。讓我們稍微擴展一下我們的類以包含 351 該行的一部分,看看它如何使用我們的請求模板。

/api/jsonplaceholder/index.js

class JSONPlaceholder {
  constructor() {
    this.endpoints = {
      posts: {
        create: (options = {}) => { ... },
        list: (options = {}) => { ... },
        post: (options = {}) => { ... },
        comments: (options = {}) => { ... },
      },
    };
  }

  posts(method = '', options = {}) {
    const existingEndpoint = this.endpoints.posts[method];

    if (existingEndpoint) {
      const endpoint = existingEndpoint(options);
      return this.request(endpoint);
    }
  }
}

export default new JSONPlaceholder();

這應該使事情更清楚一些。在這裡,我們向 363 添加了一個方法 類 373 它接受兩個參數:386397 .第一個,408 , 映射到我們的模板之一,而第二個模板 416 , 是我們可以有條件地為端點傳遞值的地方(例如,就像我們之前在定義模板時看到的帖子 ID)。

查看 421 的正文 方法,我們首先檢查是否 434 具有名稱與傳遞的 442 匹配的屬性 爭論。例如,如果 459 等於 466 答案是“是”,但如果 476 等於 480 ,它不會。

這個很重要。我們不想嘗試調用不存在的代碼。使用變量 496 , 如果我們得到一個返回值作為 507 (如果使用有效名稱,我們希望這是一個函數),接下來,我們要調用該函數以取回我們的請求模闆對象。請注意,當我們調用存儲在 519 中的函數時 ,我們傳入 521 對象。

很清楚,請考慮以下幾點:

jsonPlaceholder.posts('list', { postId: '5' });

我們調用我們的包裝器傳遞一個 535 設置為 548 .

const existingEndpoint = this.endpoints.posts['list'];

接下來,因為 555 等於 563 , 我們取回 578 功能。

(options = {}) => {
  return {
    method: 'GET',
    resource: `/posts${options.postId ? `/${options.postId}` : ''}`,
    params: {},
    body: null,
  };
}

接下來,在該函數內部,我們看到 582 已定義並將其嵌入到資源 URL 中,例如 592 .

/api/jsonplaceholder/index.js

class JSONPlaceholder {
  constructor() {
    this.endpoints = {
      posts: {
        create: (options = {}) => { ... },
        list: (options = {}) => { ... },
        post: (options = {}) => { ... },
        comments: (options = {}) => { ... },
      },
    };
  }

  posts(method = '', options = {}) {
    const existingEndpoint = this.endpoints.posts[method];

    if (existingEndpoint) {
      const endpoint = existingEndpoint(options);
      return this.request(endpoint);
    }
  }
}

export default new JSONPlaceholder();

最後,回到我們的 608 方法,我們期望得到一個 614 這是我們在 627 內部生成的請求模闆對象 .

接下來,在此之下,我們調用另一個需要定義的方法:635 , 傳入 645 我們從 650 收到的對象 .現在讓我們看一下該函數並完成我們的包裝器。

/api/jsonplaceholder/index.js

import fetch from 'node-fetch';

class JSONPlaceholder {
  constructor() {
    this.endpoints = {
      posts: {
        create: (options = {}) => { ... },
        list: (options = {}) => { ... },
        post: (options = {}) => { ... },
        comments: (options = {}) => { ... },
      },
    };
  }

  request(endpoint = {}) {
    return fetch(`https://jsonplaceholder.typicode.com${endpoint.resource}`, {
      method: endpoint?.method,
      body: endpoint?.body ? JSON.stringify(endpoint.body) : null,
    }).then(async (response) => {
      const data = await response.json();
      return data;
    }).catch((error) => {
      return error;
    });
  }

  posts(method = '', options = {}) {
    const existingEndpoint = this.endpoints.posts[method];

    if (existingEndpoint) {
      const endpoint = existingEndpoint(options);
      return this.request(endpoint);
    }
  }
}

export default new JSONPlaceholder();

很快,在我們查看新的 665 之前 方法,在頂部,請注意​​我們添加了一個 NPM 包作為依賴項:678 .在繼續之前,讓我們在我們的應用中安裝它:

終端

npm i node-fetch

接下來,讓我們仔細看看這個686 方法:

/api/jsonplaceholder/index.js

import fetch from 'node-fetch';

class JSONPlaceholder {
  constructor() {
    this.endpoints = {
      posts: {
        create: (options = {}) => { ... },
        list: (options = {}) => { ... },
        post: (options = {}) => { ... },
        comments: (options = {}) => { ... },
      },
    };
  }

  request(endpoint = {}) {
    return fetch(`https://jsonplaceholder.typicode.com${endpoint.resource}`, {
      method: endpoint?.method,
      body: endpoint?.body ? JSON.stringify(endpoint.body) : null,
    }).then(async (response) => {
      const data = await response.json();
      return data;
    }).catch((error) => {
      return error;
    });
  }

  posts(method = '', options = {}) {
    const existingEndpoint = this.endpoints.posts[method];

    if (existingEndpoint) {
      const endpoint = existingEndpoint(options);
      return this.request(endpoint);
    }
  }
}

export default new JSONPlaceholder();

現在是有趣的部分。 690 內部 方法,我們的目標是將請求模闆對像作為 709 並使用它來定制我們對 JSON 佔位符 API 發出的 HTTP 請求。

看看那個方法,我們 714 調用 728 我們從 733 導入的方法 我們剛剛安裝的包。向它傳遞我們想要向其發出 HTTP 請求的 URL。這裡,API 的“基本”URL 是 741 .使用 JavaScript 字符串插值(由我們用來定義字符串而不是單引號或雙引號的反引號表示),我們將該基本 URL 與 750 與調用匹配的模板的值。

例如,如果我們調用 763 我們期望我們傳遞給 776 的 URL 成為 789 .如果我們調用 793 ,我們希望該 URL 為 800 .

按照這個邏輯,在 URL 之後,我們將一個對像傳遞給 812 包含請求的附加選項。在這裡,我們使用 827 傳遞的模板上的屬性,以及有條件的 836 傳遞的模板上的屬性。如果 842 已定義,我們將其包含的值傳遞給 852 — 一個內置的 JavaScript 函數 — 將對象轉換為字符串(很重要,因為我們只能為 HTTP 請求正文傳遞字符串值,而不是原始對象)。

在此之後,在我們對 868 的調用結束時 我們鏈接一個 872 我們期望的回調函數 883 返回一個 JavaScript Promise。到 895 我們傳遞我們的回調函數,在 903 之前 關鍵字告訴 JavaScript “我們想使用 919 我們在這個函數內部調用的函數之一的關鍵字”(沒有這個,JavaScript 會拋出一個錯誤,說 920 是保留關鍵字)。

931 傳遞給那個回調函數——這是來自 JSON 佔位符 API 的 HTTP 響應——我們調用它的 940 方法,放置 951 正如我們所期望的那樣在前面 966 返回一個 JavaScript Promise。我們使用 977 這裡是因為我們要轉換純文本HTTP 988 我們從 API 獲取的 body 轉換為我們可以在代碼中使用的 JSON 數據。

將此結果存儲在 994 變量,我們從 1007 返回 回調將返回到 1016 1020前面的語句 然後再次冒泡回到 1030 1041前面的語句 1059 內部 方法(我們的調用來自哪裡)。反過來,這意味著我們期望得到我們的 1064 像這樣彈出:

const data = await jsonPlaceholder.posts('list');
console.log(data);
/*
[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  {
    "userId": 1,
    "id": 3,
    "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
    "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
  },
]
*/

這對我們的包裝器來說是這樣的。現在,為了看到這一點,我們將連接一些可以通過網絡瀏覽器訪問的測試路由,調用我們的包裝器來驗證響應。

定義路由來測試包裝器

為了測試我們的 API 包裝器,現在,我們將在我們自己的應用程序中連接一些路由,這些路由將通過我們的包裝器調用 JSON 佔位符 API,然後在瀏覽器中顯示我們返回的數據。

/index.server.js

import node from "@joystick.js/node";
import api from "./api";
import jsonPlaceholder from "./api/jsonplaceholder";

node.app({
  api,
  routes: {
    "/": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "/posts/create": async (req, res) => {
      const post = await jsonPlaceholder.posts('create', { title: 'Testing Posts' });
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(post, null, 2));
    },
    "/posts": async (req, res) => {
      const posts = await jsonPlaceholder.posts('list');
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(posts, null, 2));
    },
    "/posts/:postId": async (req, res) => {
      const post = await jsonPlaceholder.posts('post', { postId: req?.params?.postId });
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(post, null, 2));
    },
    "/posts/:postId/comments": async (req, res) => {
      const comments = await jsonPlaceholder.posts('comments', { postId: req?.params?.postId });
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(comments, null, 2));
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
});

這可能看起來勢不可擋,但看起來很接近。在我們的應用程序內部,當我們運行 1077 之前,一個 1086 文件是為我們設置的,我們的應用程序的 Node.js 服務器是在其中啟動的。在該文件中,1092 在後台設置 Express.js 服務器並獲取 1109 我們傳遞對象來動態生成 Express.js 路由。

在這裡,我們為該對象添加了一些測試路由,每個路由對應於我們的 API 包裝器中的一個方法。另外,在 1119 的頂部 ,我們已經導入了 1123 包裝器(請記住,我們希望這是我們的 1139 的預初始化實例 類)。

專注於我們的路線,從 1140 開始 ,在這裡,我們首先通過 1152 傳遞一個代表我們的路由處理程序的函數 前置關鍵字(同樣,這告訴 JavaScript 我們想使用 1162 聲明後的函數中的關鍵字)。

在這裡,我們創建一個變量1178 設置等於對 1181 的調用 .正如我們剛剛了解到的,如果一切正常,我們希望這會為我們對 JSON 佔位符 API 的 HTTP 請求生成模板,然後通過 1196 執行請求 ,返回給我們 1206 從響應中解析數據。在這裡,我們將該響應存儲為 1211 然後做兩件事:

  1. 設置HTTP 1223 響應我們的 Express.js 路由到 1235 的標頭 向我們的瀏覽器表明我們發送的內容是 JSON 數據。
  2. 使用 1241 的字符串化版本響應對我們路由的請求 響應(格式化為使用兩個製表符/空格)。

如果我們打開一個網絡瀏覽器,我們應該在訪問 1254 時看到這樣的內容 :

很酷,對吧?這就像我們編寫了所有代碼來執行 1260 在我們的路由處理函數中請求,但我們只需要一行代碼就可以進行調用!

如果我們仔細觀察上面的路線,它們的工作原理大致相同。注意每條路由之間的差異以及它如何改變我們對 1275 的調用 .例如,查看 1289 路線,這裡我們使用 1294 我們連接的方法需要 1309 傳入我們的包裝器調用的選項對象。為了傳遞它,在這裡,我們拉出 1311 從我們路由的參數中,並將其作為 1320 傳遞給包裝器的選項對象 .作為回報,我們會返回與我們在 URL 中指定的 ID 對應的帖子的評論:

驚人的。真快,讓我們在我們的批准印章之前對我們所有的路線進行一次現場運行:

我們終於得到它了。一個功能齊全的 API 包裝器。這種模式的優點在於我們可以將它應用到任何 我們希望標準化使用的 HTTP 或 REST API。

總結

在本教程中,我們學習瞭如何使用 Javascript 類構建 API 包裝器。我們為 JSON 佔位符 API 編寫了包裝器,學習瞭如何使用基於模板的方法來生成請求並利用單個函數通過 1333 執行該請求 .我們還學習瞭如何在我們的類上定義特定於資源的方法,以使我們的包裝器可擴展且易於使用。


Tutorial JavaScript 教程
  1. 頂級 Javascript 地圖 API 和庫

  2. 第 4 天:在 JS 中吊裝

  3. 在單獨的 JavaScript 文件中使用 Alpines Persist 插件

  4. JavaScript 等待 - 如何使用 .setTimeout() 在 JS 中休眠 N 秒

  5. 使用組件為您的應用程序引導數據

  6. 使用 Webhook 從 Github 設置自動部署

  7. Drops #01:使用 Yarn 修復依賴項中的漏洞! (或幾乎)

  1. 為什麼 Axios 很棒(以及如何開始使用它)

  2. Mithril.js 的初學者嘗試

  3. Bootstrap 5 輸入字段

  4. Javascript ascii字符串到十六進製字節數組

  5. 在組件方法中訪問 vue 全局過濾器

  6. 2. 一流的物品

  7. 在 bundle js 中包含 tailwind css

  1. Vue.js、Spring Boot、Kotlin 和 GraphQL:構建現代應用程序 - 第 2 部分

  2. Material-UI 現在是 MUI

  3. 更好的 console.logs

  4. 有用的 jQuery 或 Javascript 輪播插件