1

I'm using jsmediatags to parse metadata from mp3 files. This function grabs the title from a file. Sadly it returns before the onSuccess callback is complete. This is a problem because after this function the data is serialized and sent off BEFORE song.title is ever set.

How can I wait until jsmediatags.read is actually complete to return. (it is not an async function).

const get_song = (song_path, album) => {
    const song = {
        title: null,
    };
    jsmediatags.read(song_path, {
        onSuccess: function (tag) {
            song.title = tag?.tags?.title;
            console.info("set song fields", song.title);
        },
        onError: function (error) {}
    });
    console.info("return from get_song", song.title);
    return song;
}

This outputs

"return from get_song null",
"set song fields TITLE"

And then I use song.title before it is actually set.

How can I redesign my code so that the serialization of song happens after this callback completes.

Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95
nlta
  • 1,716
  • 1
  • 7
  • 17
  • I don't see a problem, frankly -- what do you expect to happen, of course `read` will return immediately -- it's not that it returns that signifies something has been read, it's the calling of `onSuccess` (or `onError` on error) that signifies that. You have to design your code accordingly. Do you understand the reason now why the console outputs two lines one after another? – Armen Michaeli Oct 22 '21 at 21:21
  • @amn is it possible to write a function that waits until `read` completes? Or is there some alternate design I should persue. I really do need to know it has finished before I serialize song. – nlta Oct 22 '21 at 21:24
  • Yes, it's possible, see the provided answer. – Armen Michaeli Oct 22 '21 at 21:29

2 Answers2

3

Sure, you can promisify it:

const get_song = async (song_path, album) => new Promise((resolve, reject) => {
    jsmediatags.read(song_path, {
        onSuccess: resolve,
        onError: reject
    });
});

And then use it, for instance in an async function with await:

(async () => {
    const tag = await get_song(song_path, album);

    song.title = tag?.tags?.title;
})();
Robo Robok
  • 21,132
  • 17
  • 68
  • 126
  • Thanks, I found this helpful as well https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#creating_a_promise_around_an_old_callback_api – nlta Oct 22 '21 at 21:34
  • @nlta great! Making use of Promises is so much more fun than using callbacks. – Robo Robok Oct 22 '21 at 21:37
0
const getSong = async songPath => {
    var tag = await new Promise((resolve, reject) => {
        new jsmediatags.Reader(songPath)
            .read({
                onSuccess: (tag) => {
                    console.log('Success!');
                    resolve(tag);
                },
                onError: (error) => {
                    console.log('Error');
                    reject(error);
                }
            });
    });

    return {
        title: tag?.tags?.title;
    }
}
Fatih Ertuğral
  • 230
  • 2
  • 4