Использование es6 async / await, map и fetch

Наша цель:

<icon src=”plus.svg” />

Таким образом, он отображается в DOM как

<span class=”icon”><svg>….</svg></span>

Сначала мы создаем icon.vue компонент:

<template>
  <span class="icon"></span>
</template>
<script>
export default {
  props: {
    src: { type: String, required: true }
  }
};
</script>

Мы загрузим svg в mounted, где будет просто fetch значок. С синтаксисом async/await это довольно просто.

async mounted(){
  let svg = await fetch(this.src).then(r => r.text());
  this.$el.innerHTML = svg;
}

Но когда компонент используется несколько раз, он генерирует несколько запросов к одному и тому же файлу.

<div v-for="item in list">
  {{item.desp}} 
  <button @click="add(item)">
     <icon src="plus.svg"/>
  </button>
</div>
<!-- it will make as many requests as 
     the list length to plus.svg! -->

Итак, нам нужно реализовать некоторый кеш. Мы можем сделать это через Map

let cache = new Map();
async mounted(){
  if(!cache.has(this.src)){
    cache.set(this.src, await fetch(this.src).then(r => t.text()))
  }
  this.$el.innerHTML = cache.get(this.src);
}

У нас все хорошо? Нет. Мы обнаружили, что при первой загрузке он по-прежнему выполняет несколько запросов. Проблема в том, что cache еще не заполнен, поэтому последующий запрос все равно будет выполнен. Мы должны сохранить Promise в кеше и позволить всем запросам того же src обращаться к нему.

async mounted(){
  if(!cache.has(this.src)){
    cache.set(this.src, fetch(this.src).then(r => t.text()))
  }
  this.$el.innerHTML = await cache.get(this.src);
}

Добавляем некоторую обработку ошибок:

async mounted(){
  if(!cache.has(this.src)){
    try{
      cache.set(this.src, fetch(this.src).then(r => t.text()));
    } catch (e) {
      cache.delete(this.src);
    }
  }
  if(cache.has(this.src)){
    this.$el.innerHTML = await cache.get(this.src);
  }
}

Полный код компонента:

<template>
    <span class="icon"></span>
</template>
<script>
let cache = new Map();
export default {
  props: {
    src: { type: String, required: true }
  },
  async mounted() {
    if (!cache.has(this.src)) {
      try {
        cache.set(this.src, fetch(this.src).then(r => r.text()));
      } catch (e) {
        cache.delete(this.src);
      }
    }
    if (cache.has(this.src)) {
      this.$el.innerHTML = await cache.get(this.src);
    }
  }
};
</script>

Не забудьте также упаковать свои файлы svg в конфигурацию веб-пакета.