【JS】891- 高級 Promise 模式 - Promise緩存

https://www.jonmellman.com/posts/promise-memoization  譯者:ConardLi

本文中,我們將介紹常見的緩存實現在併發條件下存在的問題。然後我們將介紹如何修復它,並且在此過程中簡化代碼。

我們將通過介紹基於 Singleton Promise 模式的 Promise Memoization 模式來做到這一點。

Singleton Promise 模式看前面的文章:高級異步模式 - Promise 單例

一個例子:緩存異步請求結果

下面是一個簡單的 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;
}, { promisetrue});

本文完。

1. JavaScript 重溫系列(22篇全)
2. ECMAScript 重溫系列(10篇全)
3. JavaScript設計模式 重溫系列(9篇全)
4.  正則 / 框架 / 算法等 重溫系列(16篇全)
5.  Webpack4 入門(上) ||  Webpack4 入門(下)
6.  MobX 入門(上)  ||   MobX 入門(下)
7. 100 +篇原創系列彙總

回覆“加羣”與大佬們一起交流學習~

點擊“閱讀原文”查看 100+ 篇原創文章

本文分享自微信公衆號 - 前端自習課(FE-study)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章