SkyNess
SkyNess

Reputation: 55

Problem with Promise.all() and .map, unexpected results

I have read everything I can about promises, I tested multiple solutions from multiple threads but I cant still get it to work

I'm trying to iterate over files in a folder, grab the xml content, convert it to a js object using xml-js module then add them to an array. This is my last attempt (one out of about 30)

fs.readdir(folderPath, async(err, files) => {
    let xmlArray = await Promise.all(
        files.map((file) => {
            fs.readFile(filePath, 'utf8', async(err, data) => {
                return await xmljs.xml2js(data , {compact : true).RequestFileHatasDM;
            })
        })
    )
    console.log(xmlArray);
})

What am I missing? I thought readdir is getting called, then the callback returns and that's where I await a Promise.all, then using .map to iterate over everything and return the value, which should (?) return with a promise to resolve.

Then after everything resolved, only then it should continue to the console.log(), but for some reason the moment I run the server it immediately jumps to console.log(), as if all the promises already resolved!

When putting console.log() inside the readFile method, it was still running.

I'm confused, can anybody care to point out my mistake?

Upvotes: 1

Views: 1337

Answers (2)

Terry Lennox
Terry Lennox

Reputation: 30685

This kind of code can be very confusing. It can make it easier to create a function that will return a Promise and accepts the file path as an input.

We can use Util.Promisify to create a promisified version of fs.readFile, it's easier to read the resulting code and is also more compact.

We can then use Promise.all on the result of mapping the file paths through this function.

For example:

const fs = require('fs');
const path = require('path')
const xmljs = require('xml-js');
const { promisify } = require ('util');

const readFilePromise = promisify(fs.readFile);
const readDirPromise = promisify(fs.readdir);

// Replace as appropriate.
const folderPath = 'test_dir';

async function xmlFile2js(path) {
    const data = await readFilePromise(path, { encoding: 'utf-8'});
    return xmljs.xml2js(data, { compact: true});
}

async function readXmlFiles(folderPath) {
    try {
        const files = await readDirPromise(folderPath);
        const filePaths = files.map(file => path.join(folderPath, file));
        const promiseArray = filePaths.map(filePath => xmlFile2js(filePath));
        const xmlArray = await Promise.all(promiseArray);
        return xmlArray;
    } catch (err) {
        console.error("readXmlFiles: An error occurred:", err);
    }
}

async function testReadXmlFiles() {
    const xmlArray = await readXmlFiles(folderPath);
    console.log("testReadXmlFiles: xmlArray:", JSON.stringify(xmlArray, null, 4));
}

testReadXmlFiles();

Upvotes: 0

grath
grath

Reputation: 66

A couple of things here. First Promise.all takes an array of promises which you're not giving it. Within your files.map you're not returning anything in that map callback function. So try this:

files.map((file) => {
        return fs.readFile(filePath, 'utf8', async(err, data) => {
            return await xmljs.xml2js(data , {compact : true).RequestFileHatasDM;
        })
    })

Also when you are returning a promise in an async function you can just do:

return xmljs.xml2js()

instead of:

return await xmljs.xml2js()

Easy thing to miss. I would encourage you to setup a debug environment so you can step through the function and watch different values in your code. It can really save you some time debugging code.

I like VSCodes debugger:

https://code.visualstudio.com/docs/nodejs/nodejs-debugging

Upvotes: 1

Related Questions