【第2723期】高級 Promise 模式:Promise緩存
前言
Promise 還可以這樣。今日前端早讀課文章由 @ConardLi 翻譯,公號:code 祕密花園授權。
正文從這開始~~
在本文中,我們將介紹常見的緩存實現在併發條件下存在的問題。然後我們將介紹如何修復它,並且在此過程中簡化代碼。
我們將通過介紹 基於 Singleton Promise 模式 的 Promise Memoization 模式來做到這一點。
一個例子:緩存異步請求結果
下面是一個簡單的 API 客户端:
const getUserById = async (userId: string): Promise<User> => { const user = await request.get(`http://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(`http://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(`http://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(`http://users-service/${userId}`); return user; });
我們採用了原始的無緩存實現,並放入了 _.memoize 包裝器,非常簡潔。
錯誤處理
對於 API 客户端,你應考慮操作可能失敗的可能性。如果我們的內存實現已緩存了被拒絕的 Promise ,則所有將來的調用都將以同樣的失敗 Promise 被拒絕!
幸運的是,memoizee (http://www.npmjs.com/package/memoizee) 庫支持此功能。我們的最後一個示例變為:
import memoize from 'memoizee'; const getUserById = memoize(async (userId: string): Promise<User> => { const user = await request.get(`http://users-service/${userId}`); return user; }, { promise: true});
關於本文
譯者:@ConardLi
譯文:http://mp.weixin.qq.com/s/sDyQh06eGrjdqAcV8XTKoQ
作者:@Jon Mellman
原文:http://www.jonmellman.com/posts/promise-memoization
關於【Promise】相關推薦,歡迎讀者自薦投稿,前端早讀課等你來