1

I'm trying to come up with a function that serves all the songs in a directory as list along with file path, duration, and last accessed time. Though the log inside the loop does print what's required but the response is being sent before the loop completes.

observation0: the log at the end happens before the log inside the loop

router.get('/', function (req, res) {

    let collection = new Array();

    // glob returns an array 'results' containg the path of every subdirectory and file in the given location
    glob("D:\\Music" + "/**/*", async (err, results) => {

        // Filter out the required files and prepare them to be served in the required format by
        for (let i = 0; i < results.length; i++) {
            if (results[i].match(".mp3$") || results[i].match(".ogg$") || results[i].match(".wav$")) {

                // To get the alst accessed time of the file: stat.atime
                fs.stat(results[i], async (err, stat) => {
                    if (!err) {

                        // To get the duration if that mp3 song
                        duration(results[i], async (err, length) => {
                            if (!err) {
                                let minutes = Math.floor(length / 60)
                                let remainingSeconds = Math.floor(length) - minutes * 60

                                // The format to be served
                                let file = new Object()
                                file.key = results[i]
                                file.duration = String(minutes) + ' : ' + String(remainingSeconds)
                                file.lastListend = moment(stat.atime).fromNow()

                                collection.push(file)
                                console.log(collection) //this does log every iteration
                            }
                        })
                    }
                })
            }
        }
        console.log(collection); //logs an empty array
    })

    res.json({
        allSnongs: collection
    });
});

I'am unable to understand the docs to an extent that would enable me to right the code myself :(

I thank you for any help and suggestions

1
  • 5
    The problem is not the combination of loops with async/await, which works totally fine, the issue is that you are passing plain old asynchronous callbacks instead of using (and awaiting) promises. Commented Aug 13, 2020 at 2:28

2 Answers 2

3

This answer doesn't fix your code, but just to clear up any miscomprehension:

fs.stat(path, callback); // fs.stat is always asynchronous.
                         // callback is not (normally), 
                         // but callback will run sometime in the future.

The only way to await functions like fs.stat which is using callbacks instead of promises, is to make the promise yourself.

function promiseStat( path ){
    return new Promise( ( resolve, reject ) => {
        fs.stat( path, ( err, stat ) => {
            if( err ) reject( err );
            else resolve( stat );
        };
    });
 }

Now we can:

const stat = await promiseStat( path );
Sign up to request clarification or add additional context in comments.

Comments

1

Had a decent amount of time to play around with your code.
I would have written it like this, I made quite a lot changes. And it works

srv.get('/', async function (req, res) {

    // glob returns an array 'results' containg the path of every subdirectory and file in the given location
    let results = await new Promise((res, rej) =>
        glob("D:\\Music" + "/**/*", (err, results) => {
            if (err != null) rej(err);
            else res(results);
        })
    );

    // Filter out the required files and prepare them to be served in the required format by
    results = results.filter(
        (result) =>
            /\.mp3$/.test(result) ||
            /\.ogg$/.test(result) ||
            /\.wav$/.test(result)
    );

    const collection = await Promise.all(
        results.map(async (result) => {
            const stat = await new Promise((res, rej) =>
                fs.stat(result, (err, stat) => {
                    if (err != null) rej(err);
                    else res(stat);
                })
            );

            const length = await new Promise((res, rej) =>
                duration(result, (err, length) => {
                    if (err != null) rej(err);
                    else res(length);
                })
            );

            const minutes = Math.floor(length / 60);
            const remainingSeconds = Math.floor(length) - minutes * 60;

            // The format to be served
            return {
                key: result,
                duration: `${minutes} : ${remainingSeconds}`,
                lastListend: moment(stat.atime).fromNow(),
            };
        })
    );

    console.log(collection);
    res.json({
        allSnongs: collection,
    });
});

gives,

[
  {
    key: 'D:\Music\1.mp3',
    duration: '0 : 27',
    lastListend: 'a few seconds ago'
  },
  {
    key: 'D:\Music\1.mp3',
    duration: '0 : 27',
    lastListend: 'a few seconds ago'
  },
  {
    key: 'D:\Music\1.mp3',
    duration: '0 : 52',
    lastListend: 'a few seconds ago'
  },
  {
    key: 'D:\Music\1.mp3',
    duration: '2 : 12',
    lastListend: 'a few seconds ago'
  }
]

2 Comments

Don't use collection.push(…) inside the callback, rather return the value and use const collection = await Promise.all(…)
@Bergi +1, thats better

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.