Javascript - обратные вызовы и обещания - разрешите jsmediatags собирать обложки альбомов перед выполнением функции - PullRequest
0 голосов
/ 03 июня 2018

Я пытался сделать что-то, что я считаю относительно простым в Javascript (на срок более 12 часов), связанный с обратными вызовами и / или обещаниями.

Heads Up: я использую Rails с.js.erb файл, но это не относится к моему вопросу, который больше сосредоточен на Javascript;показано только для иллюстрации большей проблемы.

Я использую jsmediatags для чтения метаданных из mp3 на удаленном хосте (в основном, для сбора обложек альбомов).Каждая «статья» на моем сайте соотносится с несколькими аудиофайлами (@related_titles), поэтому я перебираю каждый из них (локальная переменная url) и собираю иллюстрации, а затем отображаю их в плагине coverflow - это означает, что jsmediatags.read выполняется один раз для каждого обрабатываемого mp3.Проблема в том, что ... jsmediatags не заканчивает читать все обложки до инициализации плагина coverflow.Несмотря на то, что я относительно новичок в js, мне кажется, что это идеальное использование функции обратного вызова.

Прямо сейчас я был вынужден ввести функцию 8500ms setTimeout перед выполнением функции makeitrain(который входит в прикрытие).Он работает, но он не надежен на всех устройствах (когда они находятся под нагрузкой), и это не элегантное решение.

Давайте предположим, что в @related_titles есть 3 mp3-файла.Чтобы показать, что код не выполняется в правильном порядке (не ожидает обратного вызова?), Я добавил консольное ведение журнала.

Полученный вывод:

Gathered all art!  Now I will callback and render the Coverflow
Initing Coverflow since gatherArtwork completed/we have art!
Gathering album art for title...
Gathering album art for title...
Gathering album art for title...

Ожидаемый результат:

Gathering album art for title...
Gathering album art for title...
Gathering album art for title...
Gathered all art!  Now I will callback and render the Coverflow
Initing Coverflow since gatherArtwork completed/we have art!

Вот что я пробовал:

function gatherArtwork(_callback) {

  // Read each of the mp3s to gather artwork ('url' == local var for mp3)
  <% @related_titles.each do |url| %>
      jsmediatags.read("<%= url['mp3'] %>", {
          onSuccess: function(tag) {

              console.log('Gathering album art for title...');

              // Convert the image contents to a Base64 encoded str
              var tags = tag.tags;
              albumartwork = _arrayBufferToBase64(tags.picture["data"]);

              // Append to the coverflow list a `<ul><li> </li></ul>` for each mp3
              // The 'albumartwork' Base64 image str is also appended here
              $("#coverflow-item-list").append('<ul><li>...</li></li>');

          }
      });
  <% end %>

  console.log('Gathered all art!  Now I will callback and render the Coverflow');
  _callback();

}

// Gather artwork MUST complete fully before the `makeitrain` function runs (callback)
function renderCoverflow() {
    gatherArtwork(function() {
      console.log('Initing Coverflow since gatherArtwork completed/we have art!');
      makeitrain();
    });
}

// Execute 'renderCoverflow' which will gather art and then "makeitrain"!
renderCoverflow();

Ответы [ 2 ]

0 голосов
/ 04 июня 2018

Во-первых, вы не должны (никогда) дублировать свой js-код на сервере, как вы это сделали, результат:

function gatherArtwork() {
  // Read each of the mp3s to gather artwork ('url' == local var for mp3)
  <% @related_titles.each do |url| %>
      console.log("<%= url['mp3'] %>");
  <% end %>
}

:

function gatherArtwork() {
  // Read each of the mp3s to gather artwork ('url' == local var for mp3)
      console.log("url1");
      console.log("url2");
      ....
      console.log("urln");
}

Так что это значитчто вы отправляете много ненужного кода пользователю (теперь ваш JS-файл стал больше).Вместо этого вы можете сделать:

/**
  * Load all arts from urls
  */
function gatherArtwork(urls) {
  // Read each of the mp3s to gather artwork ('url' == local var for mp3)
  for(url in urls) {
      console.log(url);
  }
}

Преимущество использования этого также в том, что теперь вы можете объявить gatherArtwork функцию как async и сделать следующее:

async function gatherArtwork(urls) {
  for(url in urls) {
     await new Promise(function(resolve, reject) {
     jsmediatags.read("<%= url['mp3'] %>", {
       onSuccess: function(tag) {
         console.log('Gathering album art for title...');
         // Convert the image contents to a Base64 encoded str
         var tags = tag.tags;
         albumartwork = _arrayBufferToBase64(tags.picture["data"]);

         // Append to the coverflow list a `<ul><li> </li></ul>` for each mp3
         // The 'albumartwork' Base64 image str is also appended here
         $("#coverflow-item-list").append('<ul><li>...</li></li>');
         resolve(tag);
       },
       onError: function(error) {
         reject(error);
       }
    });
  });
  }
}

И затемНазовите это так:

await gatherArtwork(urls)
console.log('Gathered all art!  Now I will callback and render the Coverflow');
makeitrain();

Берегись!Функция, запускающая приведенный выше код, тоже должна быть async.если вы хотите избежать этого, вы должны использовать Promise.all

0 голосов
/ 04 июня 2018

Лучшими были бы обещания для этого.Я скопировал базовый new jsmediatags с оберткой для обещаний из https://github.com/aadsm/jsmediatags. Может потребоваться какая-то тонкая настройка, но намерение состоит в том, чтобы предоставить базовую концепцию для вас

Я не разработчик rails, поэтому оставлю это длявы можете вывести свой массив related_titles в переменную javascript, напечатав JSON, который затем автоматически прочитает javascript как массив

<script>
  var urls = // echo json array
<script>

. После этого оставшаяся часть сценария может быть помещена в отдельный файл или непосредственно встраницы, в зависимости от того, что вы предпочитаете

// helper function to get tag info and return promise that resolves with the base64 result
function getTags(url) {      
  return new Promise((resolve, reject) => {
      new jsmediatags.Reader(url)
        .read({
          onSuccess: (tag) => {                
            resolve( _arrayBufferToBase64(tag.tags.picture["data"]));
          },
          onError: (error) => {                
            reject(error);
          }
        });
    });
}

// create array of getTags() promises
let promises = urls.map(url => getTags(url))

Promise.all(promises).then(results=>{
  // `results` is array of all the base64 values same order as the urls array
  // loop over the results and add what is needed to DOM

  // then call cover flow

}).catch(err=> console.log('One of the requests failed'))
...