JavaScript >> Javascript 文檔 >  >> Node.js

如何為 Node.js 中的 Fetch 添加自動重試支持

如何在 Node.js 中為 Fetch API 編寫一個包裝函數,添加重試功能以及可選的延遲和最大嘗試次數。

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

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

終端

npm i -g @joystick.js/cli

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

終端

joystick create app

幾秒鐘後,您將看到一條消息已註銷到 cd 進入你的新項目並運行 joystick start .在你運行之前,我們需要再安裝一個依賴,node-fetch

終端

cd app && npm i node-fetch

這將使我們能夠訪問 Fetch API 的 Node.js 友好實現。安裝完成後,您可以繼續啟動您的應用程序。

終端

joystick start

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

為 Fetch 編寫一個包裝函數

首先,我們將首先編寫包裝函數以及另一個函數來幫助我們在重試嘗試之間創建延遲。因為我們會考慮像這樣的“雜項”代碼或我們應用程序的“標準庫”的一部分,所以我們將在 /lib 中創建一個文件 (“library”的縮寫)我們在上面創建的項目的根目錄。

因為我們將編寫僅適用於 Node.js 環境的代碼,所以我們將在 /lib 中創建另一個文件夾 稱為/node 這將向操縱桿發出信號,表明我們的文件應該只為節點可用的環境構建。

/lib/node/retryFetch.js

import fetch from 'node-fetch';

const retryFetch = (url = '', options = {}) => {
  const { retry = null, retryDelay = 0, retries = 5, ...requestOptions } = options;
  return fetch(url, requestOptions);
};

export default retryFetch;

上面,我們通過導入 fetch 來啟動我們的文件 我們之前通過 node-fetch 安裝的依賴項 包裹。這裡,fetch 是我們將調用以執行我們的請求的實際 Fetch 函數。在此之下,我們定義了一個函數 retryFetch 這需要兩個參數:

  1. url 這是我們要“獲取”的 URL。
  2. options 這是將移交給 fetch() 的選項對象 .

就在我們的 retryFetch 裡面 函數體,我們正在做一些特別的事情。在這裡,我們使用 JavaScript 解構來“分離”傳入的 options 目的。我們想要這樣做是因為我們要“搭載”這個對像以包含我們與重試相關的配置(Fetch 不支持這一點,所以我們不想意外地將它傳遞給 Fetch)。

為了防止這種情況,這裡我們從 options 中“提取”三個屬性 我們期待的對象:

  1. retry 一個布爾值 true 或 false ,讓我們知道如果請求失敗,我們是否應該重試請求。
  2. retryDelay 一個整數,表示在重試請求之前等待的秒數。
  3. retries 一個整數,表示我們應該在停止之前重試的次數。

在這些之後,我們編寫了 ...requestOptions 說“舀起休息 將對象轉換為名為 requestOptions 的變量 這將在此行下方可用。” 我們強調了 rest 這裡作為 ... 在 JavaScript 中被稱為“rest/spread”運算符。在這種情況下,... 字面意思是“得到休息 對象。”

為了完善我們的基礎代碼,我們返回對 fetch() 的調用 傳入 url 字符串作為第一個參數和 options 對像傳遞給我們的 retryFetch 函數作為第二個參數。

這為我們提供了基礎知識,但目前我們的 retryFetch function 是 fetch() 的無用包裝器 .讓我們擴展此代碼以包含“重試”功能:

/lib/node/retryFetch.js

import fetch from 'node-fetch';

let attempts = 0;

const retryFetch = async (url = '', options = {}) => {
  const { retry = null, retryDelay = 0, retries = 5, ...requestOptions } = options;

  attempts += 1;

  return fetch(url, requestOptions).then((response) => response).catch((error) => {
    if (retry && attempts <= retries) {
      console.warn({
        message: `Request failed, retrying in ${retryDelay} seconds...`,
        error: error?.message,
      });

      return retryFetch(url, options, retry, retryDelay);
    } else {
      throw new Error(error);
    }
  });
};

export default retryFetch;

這是該函數的大部分代碼。回到我們的 retryFetch 的主體 函數我們添加了更多代碼。首先,就在我們對 options 的解構下面 ,我們添加了一行 attempts += 1 增加 attempts 在我們的 retryFetch 之上初始化的變量 功能。這裡的想法是我們要跟踪對 retryFetch 的每次調用 這樣我們就可以在達到最大值 retries 時“退出” 允許(如果指定)。

值得注意的是,在options的解構中 ,你會注意到我們“拔掉了”retries 作為 retries = 5 .我們在這裡說的是“拔掉 retries options 的屬性 對象,如果它沒有被定義,給它一個默認值 5 。”這意味著即使我們 傳遞特定數量的retries , 默認情況下我們會嘗試 5 次然後停止(這樣可以避免我們的代碼無限運行並在無法解析的請求上浪費資源)。

接下來,請注意我們已將調用擴展到 fetch() ,這裡添加 .then().catch() JavaScript Promise 的回調(我們期望 fetch() 返回一個 JavaScript Promise)。

因為我們的目標是只處理一個失敗的 請求,對於 .then() 回調,我們只取傳遞的 response 並立即返回它(雖然技術上沒有必要——我們可以省略 .then() ——為了維護起見,這增加了我們代碼的清晰度)。

對於 .catch() ——我們真正關心的是——我們檢查是否 retry 是真的,我們的 attempts 變量的當前值小於等於指定的retries個數 (我們傳遞的或者默認的 5 )。

如果這兩件事都是真實的 ,首先,我們想通過調用 console.warn() 來提醒自己請求失敗 傳遞一個包含兩件事的對象:一條消息讓我們知道請求失敗並且我們將在分配的 retryDelay 中嘗試 以及我們從請求中收到的錯誤消息。

最重要的是,在底部,我們遞歸調用 retryFetch() 傳遞與它最初調用時完全相同的參數。

這是此功能的“技巧”。即使我們在 retryFetch 函數,我們仍然可以從自身內部調用它——trippy。請注意,我們已經為 return 添加了前綴 在前面,也是。因為我們調用的是 return 在我們原來的fetch()前面 調用,return 在我們遞歸的 retryFetch 前面 調用將“冒泡”回到 return fetch() 最終成為我們初始 retryFetch() 的返回值 打電話。

如果我們沒有 啟用重試功能或者我們已經用完了嘗試,我們採用 error 發生並拋出它(這允許它冒泡到 .catch()retryFetch() 的調用 正確)。

在我們說“完成”之前,有一個小問題。就這段代碼而言,請注意我們不是 使用 retryDelay 我們預計會通過。為了利用這一點,我們將在 retryFetch 之上編寫另一個函數 這將使我們能夠在繼續之前“暫停”我們的代碼任意秒數。

/lib/node/retryFetch.js

import fetch from 'node-fetch';

let attempts = 0;

const wait = (time = 0) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, time * 1000);
  });
};

const retryFetch = async (url = '', options = {}) => {
  const { retry = null, retryDelay = 0, retries = 5, ...requestOptions } = options;

  attempts += 1;

  return fetch(url, requestOptions).then((response) => response).catch(async (error) => {
    if (retry && attempts <= retries) {
      console.warn({
        message: `Request failed, retrying in ${retryDelay} seconds...`,
        error: error?.message,
      });

      await wait(retryDelay);

      return retryFetch(url, options, retry, retryDelay);
    } else {
      throw new Error(error);
    }
  });
};

export default retryFetch;

現在這是完整的代碼。以上retryFetch ,我們添加了另一個函數 wait 它採用 time 以秒為單位的整數並返回 JavaScript Promise。如果我們仔細觀察,返回的 Promise 內部是對 setTimeout() 的調用 取傳遞的 time 並將其乘以 1000(以獲取 JavaScript 期望的毫秒數)。 setTimeout() 內部 的回調函數,我們調用resolve() 返回的 Promise 的函數。

就像代碼建議的那樣,當 JavaScript 調用 wait() 函數,如果我們使用 await 告訴它 關鍵字,JavaScript 將“等待” Promise 解決。在這裡,該 Promise 將在指定的 time 之後解析 已經過去。酷,嗯?這樣,我們的代碼就可以異步暫停,而不會出現 Node.js 的瓶頸。

使用它非常簡單。就在我們對 retryFetch() 的遞歸調用之上 , 我們調用 await wait(retryDelay) .還要注意,我們附加了 async 我們傳遞給 .catch() 的函數的關鍵字 這樣 await 這裡不會觸發 JavaScript 中的運行時錯誤 (await 在 JavaScript 中被稱為“保留關鍵字”,除非使用它的父上下文被標記為 async,否則它將不起作用 )。

而已!讓我們編寫一些測試代碼來試一試。

調用包裝函數

為了測試我們的代碼,讓我們跳到 /index.server.js 之前運行 joystick create 時為我們創建的項目根目錄中的文件 .

/index.server.js

import node from "@joystick.js/node";
import api from "./api";
import retryFetch from './lib/node/retryFetch';

node.app({
  api,
  routes: {
    "/": (req, res) => {
      res.render("ui/pages/index/index.js", {
        layout: "ui/layouts/app/index.js",
      });
    },
    "*": (req, res) => {
      res.render("ui/pages/error/index.js", {
        layout: "ui/layouts/app/index.js",
        props: {
          statusCode: 404,
        },
      });
    },
  },
}).then(async () => {
  retryFetch('https://thisdoesnotexistatallsowillfail.com', {
    retry: true,
    retryDelay: 5,
    retries: 3,
    method: 'GET', // NOTE: Unnecessary, just showcasing passing regular Fetch options.
  }).then(async (response) => {
    // NOTE: If all is well, handle the response.
    console.log(response);
  }).catch((error) => {
    // NOTE: If the alotted number of retry attempts fails, catch the final error.
    console.warn(error);
  });
});

我們這裡要重點關注的部分是.then() 我們已經添加到 node.app() 的末尾 靠近文件底部。在裡面,我們可以看到我們正在調用導入的 retryFetch() 函數,傳遞 url 我們想調用一個字符串和一個將傳遞給 fetch() 的選項對象 .請記住,在選項對像上,我們已經告訴我們的代碼需要三個額外的選項:retry , retryDelay , 和 retries .

在這裡,我們已經指定了函數的行為以及標準 fetch() 選項 method .在我們對 retryFetch() 的調用結束時 ,我們添加一個 .then() 處理一個成功的用例,以及一個 .catch() 處理如果我們在獲得成功響應之前用完重試嘗試而返回的錯誤。

如果我們打開啟動應用程序的終端,我們應該會看到一個錯誤被打印到終端(傳遞的 URL 不存在,將立即失敗)。使用上述設置,我們應該會看到間隔 5 秒打印 3 個錯誤,然後是最後一個錯誤,讓我們知道請求最終失敗。

總結

在本教程中,我們學習瞭如何圍繞 Node.js fetch() 編寫包裝函數 允許我們指定重試邏輯的實現。我們學習瞭如何包裝 fetch() 函數同時從包裝器提供參數,以及在我們的請求失敗的情況下如何遞歸調用包裝器函數。最後,我們學習瞭如何創建一個函數,將我們的代碼延遲任意秒數,以便在請求嘗試之間暫停。


Tutorial JavaScript 教程
  1. 了解用 javascript 編寫函數的不同語法

  2. 用簡單的方法理解 React 中的單向數據綁定!

  3. 近十年回顧

  4. 圖書搜索進度

  5. 使用 Js 和 CSS 創建圖像滑塊

  6. React 中的簡易暗模式(和多種顏色主題!)

  7. 如何將數組參數傳遞給 JavaScript 中的包含方法 [關閉]

  1. ReactJS 中的受保護路由

  2. 啟動高級 WordPress 優惠券插件(幕後)

  3. 用 Redux 寫一個計數器

  4. 中級 React 開發者的壞習慣

  5. 現代 JavaScript 項目工作流/設置與 Git、CI/CD、代碼質量、工具等

  6. 10+ jQuery 粘性滾動插件

  7. 繼承人如何在 Android 設備上創建 React 應用程序

  1. React Router hooks 會讓你的組件更乾淨

  2. 如何將 AngularJS 1.x 應用程序轉換為 React 應用程序——一次一個組件。

  3. 使用電子郵件 Js 發送電子郵件

  4. 使用 Node 和 AWS Lambda 構建無服務器 Hogwarts 排序服務