Не читает FileReader

'use strict';

(function () {
  var FILE_TYPES = ['gif', 'jpg', 'jpeg', 'png'];

  function filterUploadedFiles(file) {
    var fileName = file.name.toLowerCase();

    return FILE_TYPES.some(function (ext) {
      return fileName.endsWith(ext);
    });
  }

  function uploadAvatar() {
    var avatarImg = document.querySelector('.ad-form-header__preview img');
    var avatarFileChooser = document.querySelector('#avatar');

    avatarFileChooser.addEventListener('change', function () {
      var files = Array.from(avatarFileChooser.files);
      var matchedFiles = files.filter(filterUploadedFiles);

      var reader = new FileReader();
      reader.addEventListener('load', function (evt) {
        avatarImg.src = evt.target.result;
      });

      reader.readAsDataURL(matchedFiles[0]);
    });
  }

  function uploadLocationPhotos() {
    var adFormPhotoContainer = document.querySelector('.ad-form__photo-container');
    var fileChooser = adFormPhotoContainer.querySelector('#images');
    var dummy = adFormPhotoContainer.querySelector('.ad-form__photo');

    fileChooser.addEventListener('change', function () {
      var uploadedFiles = Array.from(fileChooser.files);
      var matchedFiles = uploadedFiles.filter(filterUploadedFiles);

      function renderLocationPhotos(file) {
        var reader = new FileReader();
        reader.addEventListener('load', function (evt) {
          var template = document.querySelector('#photo').content;
          var adPhoto = template.cloneNode(true);
          var photo = adPhoto.querySelector('img');
          photo.src = evt.target.result;
          documentFragment.appendChild(adPhoto);
        });

        reader.readAsDataURL(file);
      }

      if (matchedFiles.length) {
        var documentFragment = document.createDocumentFragment();

        matchedFiles.forEach(function (file) {
          renderLocationPhotos(file);
        });

        adFormPhotoContainer.appendChild(documentFragment);
        dummy.remove();
      }
    });
  }

  uploadLocationPhotos();
  uploadAvatar();
})();


Есть такой модуль. Здесь 2 функции: одна - uploadAvatar - работает нормально, она показывает файл после его загрузки, при этом может загружаться максимум 1 файл. Вторая функция - renderLocationPhotos - должна показывать множественные фото до отправки формы после их загрузки. Если конкретнее - она получает файлы, загруженные через input[type=‘file’] multiple, затем фильтрует на предмет наличия не изображений (filterUploadedFiles) и, если в массив попали изображения, то должна считать все файлы и на основе template натиражировать кусочки разметки

<template id="photo">
    <div class="ad-form__photo">
      <img src="" width="70" height="70" alt="Фото жилья" >
    </div>
  </template>

, заполнить src данными из загруженных файлов и вставить на страницу. Начинаю понимать, что проблема снова с отложенным выполнением load у reader. Но никак не могу понять, как сделать, чтобы в разметку вставлялся уже не пустой документФрагмент, как сейчас.

Вы абсолютно правы, что проблема заключается в асинхронности. Вы вставляете фрагмент в DOM до того как произошла загрузка файла.

matchedFiles.forEach(function (file) {
    renderLocationPhotos(file);  // Здесь операция выполняется асинхронно
});
 adFormPhotoContainer.appendChild(documentFragment); // Здесь код синхронный выполниться сразу после forEach и не ждет завершения асинхронной задачи 

Так как у вас создается массив асинхронных вызовов решение задачи имеет пути:

  1. Дождаться выполнения всех асинхронных вызовов и выполнить callback (Вставку фрагмента в DOM)
  2. Выполнять callback после каждой синхронной операции

Второй способ более юзер френдли, так как картинки будут отображаться по мере их загрузки, но требует больше код рефакторинга, поэтому решим задачу первыи способом ;)

Шаги для решения:

  1. Добавить в renderLocationPhotos callback (Обвернуть в Promise)
  2. Дождаться выполнения всёх callback’ов и выполнить вставку в DOM

Реализация:
Шаг 1 модификация renderLocationPhotos:

function renderLocationPhotos(file) {
    return new Promise(function(resolve) { // 1. Функция возвращаем промис
      var reader = new FileReader();
      reader.addEventListener('load', function (evt) {
        var template = document.querySelector('#photo').content;
        var adPhoto = template.cloneNode(true);
        var photo = adPhoto.querySelector('img');
        photo.src = evt.target.result;
        documentFragment.appendChild(adPhoto);
        resolve() // 2. Разрешаем выполния промиса
      });

      reader.readAsDataURL(file);
     }) // 3. Добавленна строчка
}

Шаг 2 Так как нам нужно дождаться выполнения всех промисов мы будем использовать Promise.all который принимает аргументом массив промисов.
Чтобы создать массив промисов мы заменим forEach на map

Promise.all(matchedFiles.map(function (file) { // 1. Promise.all и map
  return renderLocationPhotos(file);           // 2. Добавили return чтобы получить массив из промисов 
})).then(function(){
   // 3. Когда все промисы выполнятся мы выполняем вставку
 adFormPhotoContainer.appendChild(documentFragment);
 dummy.remove();
})

Данное решение носит ознакомительный характер и так же не учитывает случая ошибки в FileReader

2 лайка

Спасибо огромное за такое подробное объяснение! Постараюсь вникнуть и использовать )