Асинхронное тело массива
такой вопрос. Есть задание написать простенький аудиоплеер. Я храню на локалке несколько песен, есть массив, в котором хранятся названия треков, названия которые должны выводиться на самом сайте и фотка трека. Я прохожусь по этому массиву через 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 шт):
// Симулиреум 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)
}
}()
Не стоит грузить списки последовательно друг за другом, так как это значительно увеличит время полной загрузки. Надо иметь возможность грузить максимально возможное количество треков в данный момент.
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 => {
// Отфильтруйте объекты с ошибками и показывайте нужные данные
// Последовательность сохраняется
});
}