Javascript - 回调和承诺 - 允许 jsmediatags 在执行函数之前收集专辑插图
Javascript - callbacks & promises - Allow jsmediatags to gather album artwork before executing function
我一直在尝试一些我认为在 Javascript(超过 12 小时)中与回调 and/or 承诺相关的相对简单的事情。
注意:我将 Rails 与 .js.erb
文件一起使用,但这与我的问题无关,我的问题更侧重于 Javascript;显示只是为了说明更大的问题。
我正在使用 jsmediatags 从远程主机上的 mp3 中读取元数据(主要集中在收集专辑封面)。我网站上的每个 "article" 都与多个音频文件 (@related_titles
) 相关,所以我循环遍历每个文件(局部变量 url
)并收集艺术作品,然后在 coverflow 插件中显示它们--这意味着 jsmediatags.read
对每个处理过的 mp3 执行一次。问题是……在 coverflow 插件初始化之前,jsmediatags 没有完成所有作品的阅读。尽管我对js比较陌生,但似乎完美地使用了回调函数。
现在,我被迫在执行 makeitrain
函数(初始化 coverflow)之前引入一个 8500ms setTimeout
函数。它可以工作,但在所有设备上(当它们处于负载下时)并不可靠,并且它不是一个优雅的解决方案。
假设 @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();
最好是对此做出承诺。我从 https://github.com/aadsm/jsmediatags 复制了带有承诺包装器的基本 new jsmediatags
。可能需要一些微调,但目的是为您提供基本概念大纲
我不是 rails 开发人员,所以将由您通过打印 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'))
首先,你不应该(永远)像你那样在服务器上复制你的 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
我一直在尝试一些我认为在 Javascript(超过 12 小时)中与回调 and/or 承诺相关的相对简单的事情。
注意:我将 Rails 与 .js.erb
文件一起使用,但这与我的问题无关,我的问题更侧重于 Javascript;显示只是为了说明更大的问题。
我正在使用 jsmediatags 从远程主机上的 mp3 中读取元数据(主要集中在收集专辑封面)。我网站上的每个 "article" 都与多个音频文件 (@related_titles
) 相关,所以我循环遍历每个文件(局部变量 url
)并收集艺术作品,然后在 coverflow 插件中显示它们--这意味着 jsmediatags.read
对每个处理过的 mp3 执行一次。问题是……在 coverflow 插件初始化之前,jsmediatags 没有完成所有作品的阅读。尽管我对js比较陌生,但似乎完美地使用了回调函数。
现在,我被迫在执行 makeitrain
函数(初始化 coverflow)之前引入一个 8500ms setTimeout
函数。它可以工作,但在所有设备上(当它们处于负载下时)并不可靠,并且它不是一个优雅的解决方案。
假设 @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();
最好是对此做出承诺。我从 https://github.com/aadsm/jsmediatags 复制了带有承诺包装器的基本 new jsmediatags
。可能需要一些微调,但目的是为您提供基本概念大纲
我不是 rails 开发人员,所以将由您通过打印 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'))
首先,你不应该(永远)像你那样在服务器上复制你的 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