Асинхронное тело массива

такой вопрос. Есть задание написать простенький аудиоплеер. Я храню на локалке несколько песен, есть массив, в котором хранятся названия треков, названия которые должны выводиться на самом сайте и фотка трека. Я прохожусь по этому массиву через for of и загружаю все треки. Проблема в том, что загрузка трека, на сколько я понял, асинхронная штука, а for of синхронный. Следовательно, когда я хочу добавить элемент, который будет содержать номер трека и его название, получается, что он берет номер из синхронного процесса, а, например, длительность трека, из асинхронного процесса. И у меня получается такая солянка, название стоит одного трека, время другого. Вопрос собственно в том, как сделать так, чтобы следующая итерация цикла шла только после того, как я получил данные о прошлом треке? Вот массив в котором инициализируются названия треков, картинки и названия, которые будут на самом сайте

let mass_track=[
        [
            'track_1.mp3',
            'add_modules_img.png',
            'Первая композиция'
        ],
        [
            'track_2.mp3',
            'marks_page.png',
            'Вторая композиция'
        ],
        [
            'track_3.mp3',
            'add_modules_img.png',
            'Третья композиция'
        ],
        [
            'track_3.mp3',
            'add_modules_img.png',
            'Четвертая композиция'
        ],
        [
            'track_3.mp3',
            'add_modules_img.png',
            'Пятая композиция'
        ],
    ]

Вот код добавления элемента на сайт. В итоге получается, что он берет, к примеру, index = 1, а время того трека, который быстрее подгрузится. Задача в том чтобы запускать следующую итерацию цикла только после того как у меня отработает audio.onloadedmetadata.

   for (const [index, item] of mass_track.entries()){
        let audio = new Audio('./music/' + item[0]);

        audio.onloadedmetadata = function() {
            console.log(audio)
            let second = (Math.round(this.duration) % 60 / 10) < 1 ? '0' + (Math.round(this.duration) % 60) : (Math.round(this.duration) % 60);
            $('.music__playlist').append(`
                <div class="music__element" data-music="${index}">
                    <div class="music__logo" style="background-image: url(./images/${item[1]});"></div>
                    <div class="music__element__container">
                        <div class="music__name">${item[2]}</div>
                        <div class="music__check"></div>
                        <div class="music__time">
                            <span></span>
                            <span>${Math.floor(Math.round(this.duration) / 60)}:${second}</span>
                        </div>
                    </div>
                </div>
            `);
            loaded_track.push(audio);
        };  
    }

Ответы (2 шт):

Автор решения: Alexander Lonberg

// Симулиреум Audio
class _Audio {
  constructor(duration) {
    this.duration = duration
    Object.defineProperty(this, 'onloadedmetadata', {
      // Как буд-то данные будут доступны только через 1 сек
      set(listener) {
        setTimeout(() => listener.call(this), 1000)
      }
    })
  }
}

let mass_track = [
  ['track_1.mp3', 150],
  ['track_2.mp3', 300],
  ['track_3.mp3', 50]
]

const start = Date.now()
void async function () {
  for (const [index, item] of mass_track.entries()) {
    let audio = new _Audio(item[1])

    // Задерживаем до разрешения onloadedmetadata
    let second = await Promise.resolve({
      then: (resolve) => {
        audio.onloadedmetadata = function () {
          resolve(this.duration)
        }
      }
    })

    console.log(`Прошло времени ${(Date.now() - start) / 1000} сек`, index, second)
  }
}()

→ Ссылка
Автор решения: Yugofx

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

function load(playlist) {
    Promise.all(
        // Никаких reject тут не будет, чтобы не упал весь Promise.all
        playlist.map(track => new Promise(resolve => {
            const [file, poster, name] = track;
            const audio = new Audio(`./music/${file}`);
            audio.onloadedmetadata = function() {
                resolve({ meta: this, poster, name });
            }
            audio.onerror = function() {
                resolve({ error: 'Not resolved', name });               
            }
        }))
    ).then(data => {
       // Отфильтруйте объекты с ошибками и показывайте нужные данные
       // Последовательность сохраняется
    });
}
→ Ссылка