4. 使用数据加载器
10m

概述

是时候向我们的应用程序引入数据加载器了。

在此课程中,我们将

  • 使用具有批处理功能的DataLoader
  • 更新我们的 getAmenities方法,以便将每个房源 ID 传递给数据加载器
  • 测试我们的新数据加载功能

添加数据加载器

让我们将 dataloader包导入我们的应用程序。在项目的根目录中打开一个新的终端,并运行以下命令。

odyssey-dataloaders-typescript
npm install --save dataloader

接下来,我们将跳转到我们的 ListingAPI类。在此,我们将引入一个执行以下操作的新方法:

  1. 创建一个新的 DataLoader
  2. 提供一个批处理函数,该函数收集 listingIds并向 REST API 发出单个请求

在文件顶部,从 dataloader包中导入 DataLoader

listing-api.ts
import DataLoader from "dataloader";

在我们的 ListingAPI 类中,让我们为我们的数据加载器实例化设置一个新的 private ,仅可从中获取该字段 在此类中的数据访问。

private batchAmenities = new DataLoader()

DataLoader 构造函数接受批量加载函数,我们将在内联中定义该函数。此批量函数接受一个键数组,并返回一个将解析为一个值数组的 Promise。对于键数组,我们将设置一个名为 listingIds 的参数,并且还可以添加我们的返回类型注释: Promise<Amenity[][]>

private batchAmenities = new DataLoader(
(listingIds): Promise<Amenity[][]> => {
// TODO
}
)

接下来,我们将使此函数变成 async,并 await调用 REST 端点 GET /amenities/listings 的结果。

private batchAmenities = new DataLoader(
async (listingIds): Promise<Amenity[][]> => {
await this.get<Amenity[][]>("amenities/listings");
}
);

此端点需要一个参数,该参数称为ids,它是逗号分隔的列表 ID 字符串。我们将此作为第二个添加到this.get函数,一个带有params属性的对象,传递另一个包含参数ids的对象。由于我们的数据加载器将其listingIds接收为一个数组,我们将使用join方法,指定逗号作为每个值的间隔符。

private batchAmenities = new DataLoader(
async (listingIds): Promise<Amenity[][]> => {
await this.get<Amenity[][]>("amenities/listings", {
params: {
ids: listingIds.join(","),
},
});
}
);

最后,让我们在名为amenitiesLists的新常量中捕获调用端点得到的结果,然后返回结果。

private batchAmenities = new DataLoader(
async (listingIds): Promise<Amenity[][]> => {
const amenitiesLists = await this.get<Amenity[][]>("amenities/listings", {
params: {
ids: listingIds.join(","),
},
});
return amenitiesLists;
}
);

在继续之前,让我们添加几个日志陈述,以便我们可以关注该方法的工作方式。我们将记录传入数据加载器的listingIds列表和amenitiesLists结果。

private batchAmenities = new DataLoader(
async (listingIds): Promise<Amenity[][]> => {
console.log("Making one batched call with ", listingIds);
const amenitiesLists = await this.get<Amenity[][]>("amenities/listings", {
params: {
ids: listingIds.join(","),
},
});
console.log(amenitiesLists);
return amenitiesLists;
}
);

我们的数据加载器方法已经处理好了; 现在我们需要从该类内部调用它。

更新 getAmenities

向下滚动类,我们将找到一直以来调用的getAmenities方法,即我们的Listing.amenities

与其为列表调用GET /listings/:id/amenities端点,我们将更新自己的方法,以将接收的每个列表 ID 传递给数据加载器。我们可以通过调用私有上的load方法batchAmenities来实现此目的。

getAmenities(listingId: string): Promise<Amenity[]> {
console.log("Making a follow-up call for amenities with ", listingId);
- return this.get<Amenity[]>(`listings/${listingId}/amenities`);
+ return this.batchAmenities.load(listingId);
}

我们在此更新日志陈述:与其报告我们正在提出进行后续请求,不如更改措辞,使其清楚我们将一个特定的列表 ID 传递给数据加载器。

getAmenities(listingId: string): Promise<Amenity[]> {
- console.log("Making a follow-up call for amenities with ", listingId);
+ console.log("Passing listing ID to the data loader: ", listingId);
return this.batchAmenities.load(listingId);
}

就是我们的 类!并且我们无需对我们的 进行任何更改:它将继续调用 getAmenities 方法,该方法将委派责任,将所有请求的列表 ID 中的数据收集并加载到单个 中。让我们进行测试!

测试数据加载器

确保您的服务器仍在运行,让我们返回沙盒环境

我们再次对特色列表及其便利设施运行相同的

query GetFeaturedListingsAmenities {
featuredListings {
id
title
amenities {
id
name
category
}
}
}

当我们执行 时,我们获取到的数据与之前完全一样!但让我们检查一下底层发生了什么,回到我们的终端。

首先,我们将看到三行:针对特色列表请求中涉及的每个列表 ID 各一行。

Passing listing ID to the data loader: listing-1
Passing listing ID to the data loader: listing-2
Passing listing ID to the data loader: listing-3

这些行后面紧接着输出内容来自我们的 单一 批处理调用:

Making one batched call with [ 'listing-1', 'listing-2', 'listing-3' ]

然后我们应看到打印出一个大数组:它包含多个较小的数组,每个数组又包含 多个便利设施!这些是我们请求的每个列表 ID 的便利设施列表。我们的列表 ID 已成功批处理为一个请求! 👏👏👏

练习

下列哪项陈述描述了数据加载器和解析器函数之间的关系?

要点

  • 我们为每个请求创建一个新的 DataLoader,向其构造函数传递一个批处理函数,该函数接收一个键数组,并返回一个值数组。
  • 若要实际使用数据加载器,我们调用其 load 方法并传入每个键。
  • dataloader 包具有缓存优点,并且会对作为请求的键进行重复数据删除。

恭喜!

完成这些操作后,大功告成了!您已解决 n+1 问题,有了工具包中的数据加载器,您具有提升应用程序效率的资源。我们在应用程序中采用了一种基本的数据获取策略,并将其转化为能够随着查询和功能扩展的功能。通过使用数据加载器,我们见识了如何在网络中发出更少、更有效率的请求,以比以往任何时候都更快的速度提供数据。

下一步:了解如何使用 API 以及新域增长您的内容 使用 Apollo Server 及 TypeScript 进行联合。我们将介绍构建联合 的最佳实践,以及 工具,这些工具将帮助您轻松、可观察且高性能地实现健壮的企业 API!

感谢您参加此课程,我们期待在下一课程中与您相会。

上一个

在这节课中分享你的问题和评论

本课程目前正在

测试版
.你的反馈帮助我们改进!如果您遇到了困难或感到困惑,请告诉我们,我们会帮助您。所有评论都是公开的,并且必须遵循 Apollo 行为准则。请注意,已解决或处理的评论可能会被删除。

您需要一个 GitHub 帐户才能在下面进行发帖。没有? 转而发帖至 Odyssey 论坛。