≪ 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')