首页 web前端 js教程 异步本地存储可以为您提供帮助

异步本地存储可以为您提供帮助

Dec 22, 2024 pm 10:22 PM

当您听到“异步本地存储”这个短语时,您会想到什么?您最初可能认为它指的是基于浏览器的本地存储的某种神奇实现。然而,这个假设是不正确的。异步本地存储既不与浏览器相关,也不是典型的存储机制。您可能使用过的一两个库在幕后使用它。在很多情况下,这个功能可以让你免于处理混乱的代码。

什么是异步本地存储?

异步本地存储是 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

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

前端热敏纸小票打印遇到乱码问题怎么办? 前端热敏纸小票打印遇到乱码问题怎么办? Apr 04, 2025 pm 02:42 PM

前端热敏纸小票打印的常见问题与解决方案在前端开发中,小票打印是一个常见的需求。然而,很多开发者在实...

神秘的JavaScript:它的作用以及为什么重要 神秘的JavaScript:它的作用以及为什么重要 Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

谁得到更多的Python或JavaScript? 谁得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

JavaScript难以学习吗? JavaScript难以学习吗? Apr 03, 2025 am 12:20 AM

学习JavaScript不难,但有挑战。1)理解基础概念如变量、数据类型、函数等。2)掌握异步编程,通过事件循环实现。3)使用DOM操作和Promise处理异步请求。4)避免常见错误,使用调试技巧。5)优化性能,遵循最佳实践。

JavaScript的演变:当前的趋势和未来前景 JavaScript的演变:当前的趋势和未来前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? 如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

如何实现视差滚动和元素动画效果,像资生堂官网那样?
或者:
怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? 如何实现视差滚动和元素动画效果,像资生堂官网那样? 或者: 怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? Apr 04, 2025 pm 05:36 PM

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

console.log输出结果差异:两次调用为何不同? console.log输出结果差异:两次调用为何不同? Apr 04, 2025 pm 05:12 PM

深入探讨console.log输出差异的根源本文将分析一段代码中console.log函数输出结果的差异,并解释其背后的原因。�...

See all articles