3

高级 Promise 模式:Promise缓存

 1 year ago
source link: https://www.fly63.com/article/detial/12085
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

更新日期: 2022-09-05阅读: 8标签: Promise分享

扫一扫分享

在本文中,我们将介绍常见的缓存实现在并发条件下存在的问题。然后我们将介绍如何修复它,并且在此过程中简化代码

我们将通过介绍 基于 Singleton Promise 模式 的 Promise Memoization 模式来做到这一点。

一个例子:缓存异步请求结果

下面是一个简单的 api 客户端:

const getUserById = async (userId: string): Promise<User> => {
   const user = await request.get(`https://users-service/${userId}`);
   return user;
 };

非常简单。

但是,如果要关注性能,该怎么办?users-service 解析用户详细信息可能很慢,也许我们经常使用相同的用户 ID 集来调用此方法。

我们可能要添加缓存,该怎么做?

简单的解决方案

const usersCache = new Map<string, User>();

 const getUserById = async (userId: string): Promise<User> => {
   if (!usersCache.has(userId)) {
     const user = await request.get(`https://users-service/${userId}`);
     usersCache.set(userId, user);
   }

   return usersCache.get(userId);
 };

这非常简单:在从 users-service 中解析了用户详细信息之后将结果填充到内存中的缓存中。

上面的代码,它将在以下情况下进行重复的网络调用:

await Promise.all([
   getUserById('user1'),
   getUserById('user1')
 ]);

问题在于直到第一个调用解决后,我们才分配缓存。但是,等等,如何在获得结果之前填充缓存?

如果我们缓存结果的 Promise 而不是结果本身,该怎么办?代码如下:

const userPromisesCache = new Map<string, Promise<User>>();

 const getUserById = (userId: string): Promise<User> => {
   if (!userPromisesCache.has(userId)) {
     const userPromise = request.get(`https://users-service/v1/${userId}`);
     userPromisesCache.set(userId, userPromise);
   }

   return userPromisesCache.get(userId)!;
 };

非常相似,但是我们没有 await 发出网络请求,而是将其 Promise 放入缓存中,然后将其返回给调用方。

注意,我们不需要声明我们的方法 async ,因为它不再调用 await 。我们的方法签名虽然没有改变我们仍然返回一个 promise ,但是我们是同步进行的。

这样可以解决并发条件,无论时间如何,当我们对进行多次调用时,只会触发一个网络请求 getUserById('user1') 。这是因为所有后续调用者都收到与第一个相同的 Promise 单例。

Promise 缓存

从另一个角度看,我们的最后一个缓存实现实际上只是在记忆 getUserById!给定我们已经看到的输入后,我们只返回存储的结果(恰好是一个 Promise)。

因此,记住我们的异步方法可以使我们在没有竞争条件的情况下进行缓存。

借助 lodash,我们可以将最后一个解决方案简化为:

import _ from 'lodash';

 const getUserById = _.memoize(async (userId: string): Promise<User> => {
   const user = await request.get(`https://users-service/${userId}`);
   return user;
 });

我们采用了原始的无缓存实现,并放入了 _.memoize 包装器,非常简洁。

对于 API 客户端,你应考虑操作可能失败的可能性。如果我们的内存实现已缓存了被拒绝的 Promise ,则所有将来的调用都将以同样的失败 Promise 被拒绝!

幸运的是,memoizee (https://www.npmjs.com/package/memoizee) 库支持此功能。我们的最后一个示例变为:

import memoize from 'memoizee';

 const getUserById = memoize(async (userId: string): Promise<User> => {
   const user = await request.get(`https://users-service/${userId}`);
   return user;
 }, { promise: true});
译者:@ConardLi,译文:https://mp.weixin.qq.com/s/sDyQh06eGrjdqAcV8XTKoQ
作者:@Jon Mellman,原文:https://www.jonmellman.com/posts/promise-memoization

链接: https://www.fly63.com/article/detial/12085


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK