Javascript - 回调& promises - 允许jsmediatags在执行函数之前收集专辑图片

时间:2018-06-03 20:50:28

标签: javascript callback async-await mp3

我一直在尝试一些我认为在回调和/或承诺相关的Javascript(超过12小时)中相对简单的事情。

抬头:我将Rails与.js.erb文件一起使用,但这与我的问题无关,后者更侧重于Javascript;只是为了说明更大的问题。

我正在使用jsmediatags从远程主机上的mp3中读取元数据(主要集中在收集专辑封面)。每篇"文章"在我的网站上关联多个音频文件(@related_titles),所以我循环每个文件(局部变量url)并收集艺术品,然后在coverflow插件中显示它们 - 这意味着{对于每个处理过的mp3,执行一次{1}}。问题是......在封面流插件初始化之前,jsmediatags没有读完所有的艺术作品。即使我对js相对较新,但它似乎是回调函数的完美用法。

现在,在执行jsmediatags.read功能(在封面流程中)之前,我被迫引入了8500ms setTimeout函数。它可以工作,但它在所有设备上都不可靠(当它们处于负载状态时)并且它不是一个优雅的解决方案。

我们假设makeitrain中有3个mp3。为了表明代码没有以正确的顺序执行(不等待回调?),我已经添加了控制台日志记录。

收到的输出:

@related_titles

预期输出:

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!

2 个答案:

答案 0 :(得分:0)

最好的将是承诺。我使用来自https://github.com/aadsm/jsmediatags的承诺包装器复制了基本的new jsmediatags。可能需要一些微调,但意图是为您提供基本的概念大纲

我不是一个rails dev所以会留下你通过打印JSON将你的related_titles数组输出到一个javascript变量,然后由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'))

答案 1 :(得分:0)

首先,你不应该像以前一样在服务器上复制你的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