首頁 >web前端 >js教程 >非同步本地儲存可以為您提供協助

非同步本地儲存可以為您提供協助

Mary-Kate Olsen
Mary-Kate Olsen原創
2024-12-22 22:22:14661瀏覽

當您聽到「非同步本地儲存」這個短語時,您會想到什麼?您最初可能認為它指的是基於瀏覽器的本地儲存的某種神奇實現。然而,這個假設是不正確的。非同步本地儲存既不與瀏覽器相關,也不是典型的儲存機制。您可能使用過的一兩個庫在幕後使用它。在很多情況下,這個功能可以讓你免於處理混亂的程式碼。

什麼是異步本地儲存?

非同步本地儲存是 Node.js 中引入的功能,最初在 v13.10.0 和 v12.17.0 版本中添加,後來在 v16.4.0 中穩定下來。它是 async_hooks 模組的一部分,該模組提供了一種追蹤 Node.js 應用程式中的非同步資源的方法。該功能允許創建多個非同步函數可以存取的共享上下文,而無需明確傳遞它。上下文在傳遞給 AsyncLocalStorage 實例的 run() 方法的回呼中執行的每個(也是唯一)操作中可用。

使用 AsyncLocalStorage 的模式

在深入範例之前,讓我們先解釋一下我們將使用的模式。

初始化

import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const asyncLocalStorage = new AsyncLocalStorage<Context>();

// export const authAsyncLocalStorage = new AuthAsyncLocalStorage<AuthContext>()

在上面的模組中,我們初始化 AsyncLocalStorage 的實例並將其匯出為變數。

用法

asyncLocalStorage.run({ userId }, async () => {
  const usersData: UserData = await collectUsersData();
  console.log("usersData", usersData);
});

// (method) AsyncLocalStorage<unknown>.run<Promise<void>>(store: unknown, callback: () => Promise<void>): Promise<void> (+1 overload)

run() 方法有兩個參數:儲存(包含我們想要共享的資料)和回呼(我們放置邏輯的位置)。因此,回調中的每個函數呼叫都可以存取存儲,從而允許跨非同步操作無縫共享資料。

async function collectUsersData() {
  const context = asyncLocalStorage.getStore();
}

要存取上下文,我們匯入實例並呼叫 asyncLocalStorage.getStore() 方法。最棒的是,從 getStore() 檢索的儲存是類型化的,因​​為我們在初始化期間將 Context 類型傳遞給了 AsyncLocalStorage: new AsyncLocalStorage().

非同步本地儲存作為身份驗證上下文

沒有身份驗證系統的 Web 應用程式。我們必須驗證身份驗證令牌並提取使用者資訊。一旦我們獲得了用戶身份,我們希望使其在路由處理程序中可用,並避免在每個處理程序中重複程式碼。讓我們看看如何利用 AsyncLocalStorage 來實現身份驗證上下文,同時保持程式碼整潔。

我在這個範例中選擇了 fastify。

根據文件 fastify 是:

快速且低開銷的 Web 框架,適用於 Node.js

好的,讓我們開始吧:

  1. 安裝 fastify:
import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const asyncLocalStorage = new AsyncLocalStorage<Context>();

// export const authAsyncLocalStorage = new AuthAsyncLocalStorage<AuthContext>()
  1. 定義我們的身份驗證上下文的類型:
asyncLocalStorage.run({ userId }, async () => {
  const usersData: UserData = await collectUsersData();
  console.log("usersData", usersData);
});

// (method) AsyncLocalStorage<unknown>.run<Promise<void>>(store: unknown, callback: () => Promise<void>): Promise<void> (+1 overload)
  1. 初始化 AsyncLocalStorage 的實例,將其指派給變量,然後匯出該變數。請記得傳遞相關類型:new AsyncLocalStorage()。
async function collectUsersData() {
  const context = asyncLocalStorage.getStore();
}
  1. 初始化 Fastify 實例並新增用於錯誤處理的實用程式:
npm install fastify

現在到了非常重要的部分。我們將新增一個 onRequest 鉤子來使用 authAsyncLocalStorage.run() 方法包裝處理程序。

type Context = Map<"userId", string>;

成功驗證後,我們從 authAsyncLocalStorage 呼叫 run() 方法。作為儲存參數,我們傳遞身份驗證上下文以及從令牌中檢索到的 userId。在回調中,我們呼叫done函數來繼續Fastify生命週期。

如果我們有需要非同步操作的身份驗證檢查,我們應該將它們加入回調。這是因為,根據文件:

使用 async/await 或返回 Promise 時,done 回呼不可用。如果您在這種情況下呼叫完成回調,則可能會發生意外行為,例如處理程序的重複呼叫

這是一個範例:

import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const authAsyncLocalStorage = new AsyncLocalStorage<Context>();

我們的範例只有一條受保護的路線。在更複雜的場景中,您可能需要僅使用身份驗證上下文包裝特定路由。在這種情況下,您可以:

  1. 將 onRequest 掛鉤包裝在僅套用於特定路由的自訂外掛程式中。
  2. 在 onRequest 鉤子本身內加入路由區分邏輯。

好吧,我們的上下文已經設定完畢,我們現在可以定義一條受保護的路由:

import Fastify from "fastify";

/* other code... */

const app = Fastify();

function sendUnauthorized(reply: FastifyReply, message: string) {
  reply.code(401).send({ error: `Unauthorized: ${message}` });
}

/* other code... */

程式碼非常簡單。我們導入 authAsyncLocalStorage,檢索 userId,初始化 UserRepository 並取得資料。這種方法使路由處理程序保持乾淨和專注。

探索 Next.js 如何使用非同步本地存儲

在此範例中,我們將重新實作 Next.js 中的 cookies 助理。但等等,這是一篇關於 AsyncLocalStorage 的文章,對嗎?那我們為什麼要談論cookie呢?答案很簡單:Next.js 使用 AsyncLocalStorage 來管理伺服器上的 cookie。這就是為什麼在伺服器元件中讀取 cookie 如此簡單:

import Fastify from "fastify";
import { authAsyncLocalStorage } from "./context";
import { getUserIdFromToken, validateToken } from "./utils";

/* other code... */

app.addHook(
  "onRequest",
  (request: FastifyRequest, reply: FastifyReply, done: () => void) => {
    const accessToken = request.headers.authorization?.split(" ")[1];
    const isTokenValid = validateToken(accessToken);
    if (!isTokenValid) {
      sendUnauthorized(reply, "Access token is invalid");
    }
    const userId = accessToken ? getUserIdFromToken(accessToken) : null;

    if (!userId) {
      sendUnauthorized(reply, "Invalid or expired token");
    }
    authAsyncLocalStorage.run(new Map([["userId", userId]]), async () => {
      await new Promise((resolve) => setTimeout(resolve, 2000));
      sendUnauthorized(reply, "Invalid or expired token");
      done();
    });
  },
);

/* other code... */

我們使用next/headers導出的cookies函數,它提供了多種管理cookies的方法。但這在技術上怎麼可能呢?

現在是時候開始我們的重新實施了”

首先,我想提一下,這個範例是基於我從 Lee Robinson 的精彩影片以及深入研究 Next.js 儲存庫中獲得的知識。

在此範例中,我們將使用 Hono 作為我們的伺服器框架。我選擇它有兩個原因:

  1. 我只是想嘗試看看。
  2. 它為 JSX 提供了堅實的支援。

先安裝Hono:

import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const asyncLocalStorage = new AsyncLocalStorage<Context>();

// export const authAsyncLocalStorage = new AuthAsyncLocalStorage<AuthContext>()

現在,初始化Hono並加入中間件:

asyncLocalStorage.run({ userId }, async () => {
  const usersData: UserData = await collectUsersData();
  console.log("usersData", usersData);
});

// (method) AsyncLocalStorage<unknown>.run<Promise<void>>(store: unknown, callback: () => Promise<void>): Promise<void> (+1 overload)

程式碼類似 Fastify 範例中的中間件,不是嗎?為了設定上下文,我們使用 setCookieContext,它是從 cookies 模組匯入的 - 我們自訂的 cookies 函數的簡單實作。讓我們跟著 setCookieContext 函數並導航到導入它的模組:

async function collectUsersData() {
  const context = asyncLocalStorage.getStore();
}

setCookieContext 函數(其回傳值我們傳遞給Hono 中間件中的cookieAsyncLocalStorage.run())從代表hono 上下文的c 參數中提取cookie,並將它們與提供用於管理cookie 的實用函數的閉包捆綁在一起。

我們的 cookie 功能複製了 next/headers 中 cookie 的功能。它利用 cookieAsyncLocalStorage.getStore() 方法來存取呼叫時傳遞給 cookieAsyncLocalStorage.run() 的相同上下文。

我們將 cookies 函數的回傳包裝在一個承諾中,以模仿 Next.js 實現的行為。在版本 15 之前,此函數是同步的。現在,在目前的 Next.js 程式碼中,cookie 返回的方法被附加到一個 Promise 對象,如以下簡化範例所示:

npm install fastify

值得一提的另一點是,在我們的例子中,使用cookies.setCookie 和cookies.deleteCookie 總是會拋出錯誤,類似於在伺服器元件中設定cookie 時在Next.js 中觀察到的行為。我們硬編碼了這個邏輯,因為在原來的實作中,我們是否可以使用setCookie或deleteCookie取決於儲存在稱為RequestStore的儲存中的phase(WorkUnitPhase)屬性(這是AsyncLocalStorage的實現,也儲存cookie)。然而,這個主題更適合另一篇文章。為了讓這個範例簡單,我們省略 WorkUnitPhase 的模擬。

現在我們需要加入 React 程式碼。

  1. 新增App組件:
type Context = Map<"userId", string>;
  1. 新增管理cookie的元件:
import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const authAsyncLocalStorage = new AsyncLocalStorage<Context>();

cookie 的用法與 Next.js React 伺服器元件中的用法類似。

  1. 新增路由處理程序來渲染範本:
import Fastify from "fastify";

/* other code... */

const app = Fastify();

function sendUnauthorized(reply: FastifyReply, message: string) {
  reply.code(401).send({ error: `Unauthorized: ${message}` });
}

/* other code... */

我們的模板是透過 hono 上下文中的 html 方法渲染的。這裡的關鍵點是路由處理程序在 asyncLocalStorage.run() 方法中運行,該方法採用 cookieContext。這樣一來,我們就可以在 DisplayCookies 元件中透過 cookies 函數來存取這個上下文了。

不可能在React伺服器元件中設定cookie,所以我們需要手動設定:

Async Local Storage is Here to Help You

讓我們來刷新一下頁面:

Async Local Storage is Here to Help You

現在,我們的 cookie 已成功檢索並顯示。

結論

asyncLocalStorage 還有更多用例。此功能允許您在幾乎任何伺服器框架中建立自訂上下文。 asyncLocalStorage 上下文封裝在 run() 方法的執行中,使其易於管理。它非常適合處理基於請求的場景。該 API 簡單而靈活,透過為每個狀態建立實例來實現可擴展性。可以無縫地維護身份驗證、日誌記錄和功能標誌等單獨的上下文。

儘管它有很多好處,但仍有一些注意事項需要牢記。我聽說 asyncLocalStorage 在程式碼中引入了太多「魔力」。我承認,當我第一次使用此功能時,我花了一些時間才完全掌握這個概念。另一件需要考慮的事情是將上下文匯入模組會建立一個您需要管理的新依賴項。然而,最終,透過深度嵌套的函數呼叫傳遞值要糟糕得多。

感謝您的閱讀,我們下一篇文章見! ?

PS:您可以在這裡找到範例(另加一個獎勵)

部落格文章來源:https://www.aboutjs.dev/en/async-local-storage-is-here-to-help-you

以上是非同步本地儲存可以為您提供協助的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn