Map.getOrInsert()やMap.getOrInsertComputed()で効率よくMapを使う
UpsertのプロポーザルがStage 4に到達し、Map#getOrInsertやMap#getOrInsertComputedが主要ブラウザで利用できるようになった。
Map内にキーが存在すればその値を返し、存在しなければデフォルト値を返すメソッドだ。キャッシュや遅延初期化などで活用できる。
const map = new Map([
['0001', { name: 'Alice' }],
['0002', { name: 'Bob' }],
['0003', { name: 'Chris' }]
])
// { name: 'Alice' }
console.log(map.getOrInsert('0001', { name: 'Dave' }))
// { name: 'Dave' }
console.log(map.getOrInsert('0004', { name: 'Dave' }))
// ↓以下と同じ
const name1 = (() => {
if (map.has('0001') === false) {
map.set('0001', { name: 'Dave' })
}
return map.get('0001')
})()
Map#getOrInsertは、キーが存在するかどうかに関係なくデフォルト値を評価するため、巨大なMapを扱うときにパフォーマンスの問題が発生する可能性がある。
Map#getOrInsertComputedは、キーが存在しない場合のみデフォルト値を評価するため、パフォーマンスの問題を回避できる。
const defaultCreator = key => {
console.log(`Creating default value for ${key}`)
return { name: 'Dave' }
}
// { name: 'Alice' }
console.log(map.getOrInsertComputed('0001', defaultCreator))
// { name: 'Dave' }
console.log(map.getOrInsertComputed('0004', defaultCreator))
ユースケース: ストリーム処理(Map#getOrInsert())
初期化処理が軽量であればMap#getOrInsertが有用だ。
type Event = {
type: string
payload: any
}
const logs = new Map<string, payload[]>()
const onEvent = (event: Event) => {
logs.getOrInsert(event.type, []).push(event.payload)
}
onEvent({ type: 'click', payload: { x: 100, y: 200 } })
onEvent({ type: 'click', payload: { x: 150, y: 250 } })
onEvent({ type: 'scroll', payload: { scrollTop: 300 } })
console.log(logs)
// Map {
// 'click' => [
// { x: 100, y: 200 },
// { x: 150, y: 250 }
// ]
// 'scroll' => [
// { scrollTop: 300 }
// ]
// }
ユースケース: キャッシュ、メモ化
cache(Mapオブジェクト)にデータがあればそれを返し、なければデータを取得して返すようなキャッシュを実装するのに便利。
Map#getOrInsertやMap#getOrInsertComputedのコールバックでPromiseを返してもMapにPromiseが格納されるだけで、解決したあとの値が格納されるわけではない点に注意が必要だ。
const cache = new Map()
const getUser = async (id: string) => {
return cache.getOrInsertComputed(id, async (id: string) => {
const res = await fetch(`/api/users/${id}`)
const json = await res.json()
return json.user
})
}
const user = await getUser('0001')