INOYAKAIGOR

Внимание! Сайт незапланированно сломался и вскорости я его починю. Местами он уже, вроде, работает.

Загрузка нескольких файлов с превью на Javascript промисах


Запись от: 2017-04-28 16:55:00

   Буквально недавно передо мной стала задача загрузка нескольких картинок. Казалось бы, плёвое дело, но мне захотелось сделать это ещё и удобно. Поэтому я решил, что перед загрузкой надо показать пользователю превью добавленных (но, ещё не загруженных) картинок.

Ниже приведена часть кода ответственная за это:


  readFile = file => {
    let fileReader = new FileReader()

    return new Promise((resolve, reject) => {
      fileReader.onload = e => {
        let dataURI = e.target.result
        resolve(<ItemPreview src={dataURI} preview={true}/>)
      }

      fileReader.onerror = () => reject('Ошибка чтения файла')

      if (/^image/.test(file.type)) {
        fileReader.readAsDataURL(file)
      } else {
        reject('Можно загружать только изображения!')
      }
    })
  }

  onAddFiles = e => {
    const {files} = e.dataTransfer
    this.setState({files: [...files]})

    let a = [].map.call(files, file => this.readFile(file))

    Promise.all(a)
    .then(previews => this.setState({items: [...previews, ...this.state.items]}))
    .catch(alert)
  }


Пара замечаний:

  • Во-первых, загрузка происходит как с помощью классического окна выбора файлов, так и с помощью драг'н'дропа.
  • Во-вторых, как некоторые уже поняли, это код на React. Компонент на который происходит дроп файлов я опустил ибо его реализация не суть этой статьи. В коде также виден компонент который отображает как уже загруженнвые файлы, так и только что добавленные. Его реализацию я тоже опущу ибо это просто dumb-компонент


Что происходит в коде?


Начну с функции readFile()


Это обычное замыкание, которое возвращает промис, который, в свою очередь, читает файл через новый инстанс интерфейса FileReader (почитать о нём можно на MDN) и возвращает его dataURI, чтобы потом подставить полученный результат в атрибут src тэга <img>.

Внутри этого промиса к инстансу FileReader'а добавляется два обработчика: на успешное чтение файла (onload) и на ошибку чтения (onerror). В них вызываются функции переданные как параметры в промис: resolve() и reject(). В качестве последнего, для примера, я использовал обычный alert(). В качестве resolve() выступает встроенная функция компонентов React: setState(). Вот тут есть важный нюанс о котором я расскажу ниже
Дополнительно происходит проверка на тип файла, но это, что очевидно, не обязательно.

Обработчик события добавления файлов


- он же onAddFiles().
Первые две строчки моджно смело пропустить, потому, что в них берутся выбранные файлы и добавляются в список для последующей загрузки.
Дальше мы собираем в массив a[] пачку промисов которые будут читать добавленные файлы и резолвиться/режектиться в зависимости от результата.

И вот тут наступает тот момент о котором я обещал рассказать выше.

Для начала предлагаю задуматься как можно написать код который будет обрабатывать список файлов с помощью имеющейся у нас функции readFile()? Первое что приходит на ум это перебор файлов в цикле с вызовом для каждого файла этой функции, а затем обновлять стейт компонента через setState().

Только вот беда, на каждую загрузку файла будет происходить обновление стейта и, как следствие, перерисовка компонента включая его потомков, а это минус к производительности, которой так известен React.

И вот тут к нам на помощь приходит волшебная функция Promise.all которая принимает на вход массив промисов и возвращает массив результатов, но только после того, как разрешаться с каким-либо резальтатом ВСЕ переданные в него промисы. Именно для этого я собрал их в массив на третьей строке функции onSelectFiles(). После того, как промисы отработали, в качестве resolve() вызывается функция setState() которая перерисовывавает компонент лишь единожды.

Как говорили раньше в интернетах: PROFIT!!1

Теги: react javascript es6 promise промисы filereader 

Комментарии:



©Игорь InoY Звягинцев