≪ Today I learned. RSS購読
公開日
タグ
JavaScript
著者
ダーシノ

Map.getOrInsert()やMap.getOrInsertComputed()で効率よくMapを使う

UpsertのプロポーザルがStage 4に到達し、Map#getOrInsertMap#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#getOrInsertMap#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')