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


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

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)
}


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


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


Начну с функции 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