Using Promises with readdir to diplay directory - javascript

I know that there are better ways to do this and tools such as serve-index already exist as an npm package to list a directory structure, but I wanted to test this out on my as I am still rather new to using Node.js and JavaScript.
Currently, my code is as follows, and I am confused as to why my array is empty.
let directoryTree = [];
let directoryList = new Promise((resolve, reject) => {
resolve(fs.readdir(path.join(__dirname, '..'), (err, fileList) => {
for (file of fileList) {
console.log(file);
directoryTree.push(file);
}
}));
}).then(() => console.log(directoryTree))
.catch((err) => console.log(error));
What gets displayed is
[]
app
files
node_modules
package-lock.json
package.json
This indicates to me that the promise is being resolved before readdir finishes executing, otherwise the empty array wouldn't be logged first. I believe that something may be incorrect in my usage of promises then, but I might be wrong. I appreciate any advice!

You can call resolve() to resolve the promise when the callback is done fetching the data asynchronously.
Then when the data is fetched and resolved you would get the list of files in the then() callback:
new Promise((resolve, reject) => {
fs.readdir(path.join(__dirname, '..'), (err, fileList) => {
err ? reject(err) : resolve(fileList); //resolve with list of files
});
})
.then((list) => console.log(list))
.catch((err) => console.log(err));

You're using Promise incorrectly.
The syntax is (from MDN):
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
resolve(someValue); // fulfilled
// or
reject("failure reason"); // rejected
});
Refactoring:
let directoryList = new Promise((resolve, reject) => {
fs.readdir(path.join(__dirname, '..'), (err, fileList) => {
if (err) reject(err)
else resolve(fileList)
});
})
// directoryList is a Promise that could reject or resolve
directoryList.then(() => console.log(directoryTree))
.catch((err) => console.log(error));

Related

Promise function doesn't trigger after another promise

I'm working on a microcontroller that would either take docx files or html strings in input and would transform it into a singular pdf file and return its link as an ouput.
My code looks like this so far:
// 'files' is an array of uploaded docx files.
const uploaded = files.map((file) => {
return new Promise((resolve, reject) => {
pump(
file.toBuffer(),
fs.createWriteStream(join(__dirname, 'files', file.filename))
.on('finish', resolve)
)
})
})
Promise.all(uploaded)
// Is triggered
.then(async () => await convertFiles())
// Is not triggered
.then(async () => {
// concatStoreFiles() is an external function because I need it somewhere else too
test = await concatStoreFiles(join(__dirname, 'files'))
console.log({test})
res.send(test)
})
const convertFiles = () => {
return new Promise((resolve, reject) => {
const cmd = `soffice --headless --convert-to pdf --outdir ${join(__dirname, 'files')} ${join(__dirname, 'files', '*.*')}`
exec(cmd, (error, stdout, stderror) => {
if (error) console.warn(error)
resolve(stdout ?? stderror)
})
})
}
concatStoreFile.js
module.exports = async function concatFiles (dirPath, outPath) {
return new Promise ((resolve, reject) => {
const existingFiles = []
fs.readdir(dirPath, (e, files) => {
files.forEach((file) => {
// is added to the files list only if finishing with ".pdf"
if (/[\d\w_-]+.pdf/.matches(file)) {
existingFiles.push(file)
}
});
resolve(existingFiles)
})
})
}
I'm working with Insomnia for my development / test process, and it tells me that I get an empty response. However, I'm supposed to get an array of pdf files existing in a specific directory. I'm not even getting console.log({test}), so I don't think my second then() is triggered.
I'm really rusty with async / await and Promise syntaxes, what should I do in this situation?
Thank you in advance
The #fastify/multipart's toBuffer() API returns a Promise, not a buffer. Checkout this article
So you need to write something like:
const uploaded = files.map(processFile)
async function processFile (file) {
const buffer = await file.toBuffer()
const storedFileName = join(__dirname, 'files', file.filename)
const writeStream = fs.createWriteStream(storedFileName)
return new Promise((resolve, reject) => {
pump(buffer, writeStream, (err) => {
if(err) { return reject(err) }
resolve(storedFileName)
})
}
}
Moreover, to improve the code, I returned the storedFileName instead of recalculating it.
You can convert this:
.then(async () => await convertFiles())
to this:
.then(() => convertFiles())
Mixing async/await and promise then/catch leads to hidden bugs hard to find

How to make a promise clean up after it has been used

I have an async function that creates a folder and I want to be able to do a computation with this folder and then have the same function remove it.
What I have tried:
const t = async () => {
let createdFolderPath = ''
return new Promise(async (resolve, reject) => {
createdFolderPath = await createAFolder()
console.log('Folder Created')
resolve(createdFolderPath)
}).finally(async () => {
await deleteCreatedFolder(createdFolderPath)
console.log('Deleted Folder')
})
}
t().then(async (folderpath) => {
await doSomethingWithFolderThatIsAsync(folderPath)
console.log('Computation Done')
})
What I want to happen is:
Folder Created
Computation Done
Deleted Folder
What actually happens is:
Folder Created
Deleted Folder
Computation Done
I want the t function to also remove the folder so the person using it doesn't have to worry about removing the folder.
I'd pass the .then callback into t instead of using the returned Promise from t. You should also avoid the explicit Promise construction antipattern:
const t = async (thenCallback) => {
let createdFolderPath = ''
return createAFolder()
.then(thenCallback)
.finally(async () => {
await deleteCreatedFolder(createdFolderPath)
console.log('Deleted Folder')
})
}
t(async (folderpath) => {
await doSomethingWithFolderThatIsAsync(folderPath)
console.log('Computation Done')
})
.then(() => {
// everything finished
});

Page rendering before Promise resolves in Node, Express, mongoDB

I have removed error handling code as it is working (for simplicicty).
I have a function createOrder() which creates a new record in my MongoDB collection:
createOrder = order => new Promise((resolve, reject) => {
Order.create({orderId: order});
resolve();
});
getOrders() function that adds all current orders in the database to an array.
getOrders = _ => new Promise((resolve, reject) => {
Order.find({buyerId: curUser.userId}, (err, orders) => {
orders.map(order => order.status != 'cancelled' ? curUserOrders.push(order) : null);
});
resolve();
});
An external javascript file to post an order:
postOrder = () => {
window.location = `/new_order/order=${JSON.stringify(itemArray)}`;
}
and the node application that "gets" the request:
app.get("/new_order/order=:order", (req, res) => {
createOrder(JSON.parse(req.params.order))
.then(getOrders)
.then(_ => res.render("userprofile", {user : curUser, orders: curUserOrders}));
});
when i post a new order the postOrder() function is triggered and "userprofile" is displayed but none of the orders are displayed until i refresh the page again. Im still learning how promises work so im guessing the problem lies there. Please help :).
Promises are used for asynchronous operations, but your example resoves the promise right away.
createOrder = order => new Promise((resolve, reject) => {
Order.create({orderId: order});
resolve();
});
You would write the create function like:
createOrder(order).then(()=>{
// The asynchronous operation is completed!
})
But in your case it would happen the following:
createOrder = order => new Promise((resolve, reject) => {
//Start the asynchronous operation ....
Order.create({orderId: order});
//... but you just resolve the operation right away.
resolve();
});
A better example is probably the other function:
getOrders = _ => new Promise((resolve, reject) => {
Order.find({buyerId: curUser.userId}, (err, orders) => {
orders.map(order => order.status != 'cancelled' ? curUserOrders.push(order) : null);
});
// You just resolve without waiting for the callback of find.
resolve();
});
What you want:
getOrders = _ => new Promise((resolve, reject) => {
Order.find({buyerId: curUser.userId}, (err, orders) => {
orders.map(order => order.status != 'cancelled' ? curUserOrders.push(order) : null);
// Here we call resolve inside the callback and we wait for the find method and return the orders to the getOrders().then(orders => {}) call.
resolve(orders);
});
});
A Promise is what the name suggests, it provides you with a promise, that it will be fulfilled (resolved) at some point in the future. So you always want to wait with the resolve function until all operations are completed without errors.
Use it as callback like this
createOrder = order => new Promise((resolve, reject) => {
Order.create({orderId: order}).then(()=>{resolve});
});

Issue resolving all promises

I have a code block where I want to concatenate results of two database queries. So I tried implementing Promises.all
const promise_list = []
let appData = [];
let promise = new Promise((resolve, reject) => {
let database = new Database();
database.query(`select * from configuration where version = (select max(version) from configuration) OR version= ? ORDER BY version `, [req.body.app_version])
.then(rows => {
appData=rows[0];
database.close()
resolve()
}, err => {
return database.close().then(() => { throw err; })
})
.catch(err => {
console.log(err);
res.status(500).json("Database Error");
reject()
})
});
promise_list.push(promise)
let promise2 = new Promise((resolve, reject) => {
let database = new Database();
database.query(`select points from users where id=?`, [req.user.id])
.then(rows => {
appData.points=rows[0]['points'];
database.close()
resolve()
}, err => {
return database.close().then(() => { throw err; })
})
.catch(err => {
console.log(err);
res.status(500).json("Database Error");
reject()
})
});
promise_list.push(promise2)
Promise.all(promise_list).then(result => {
res.status(200).json(appData);
});
The second query works sometimes and sometimes it doesnt. What could be the issue?
appData.points=rows[0]['points']; only works if appData has been initialized by the other promise first. But with Promise.all, either promise can be resolved first. If the first promise resolve second, it will simply override whatever value appData currently has.
It looks like you are using promises incorrectly. Instead of using them with side-effects (assigning to appData), you should resolve them properly.
The whole code can be cleaned up a lot to something like this:
let database = new Database();
let promise = database.query(`select * from configuration where version = (select max(version) from configuration) OR version= ? ORDER BY version `, [req.body.app_version])
.then(rows => rows[0]);
let promise2 = database.query(`select points from users where id=?`, [req.user.id])
.then(rows => rows[0].points);
Promise.all([promise, promise2])
.then(
([appData, points]) => {
appData.points = points;
res.status(200).json(appData);
},
err => {
console.log(err);
database.close().then(() => {
res.status(500).json("Database Error");
});
}
);
Don't know what Database does, so it's not clear whether it's OK to only call it once. But it should you a better idea for how to use promises.

Convert callback to Promise to download multiple files

I'm trying to download several files from a server. I ran into error memory leak with for, forEach and map, so I use this callback function and it works:
.then(files => {
const downloadFile = callback => {
if (files.length > 0) {
let file = files.shift();
client.download(`/${file}`, `./${file}`, () => {
console.log(`Downloaded: ${file}`);
downloadFile(callback);
});
}
else {
callback();
}
};
downloadFile(() => {
console.log('All done');
})
})
I'd like to convert it into a Promise function but I'm stuck, I've tried new Promise((resolve, reject)=>{}) and Promise.all() but it only returns the first file.
You can use map() with your files array to produce one promise for each file. Then you can can call Promise.all() to know when they have all been downloaded.
Since we don't know anything about your client.download() method, we are guessing that it's doing what it should. It's strange that it's callback doesn't take a parameter with the actual file data.
let promises = files.map(file => {
return new Promise((resolve, reject) => {
client.download(`/${file}`, `./${file}`, () => {
// should this call back have some data or error checking?
console.log(`Downloaded: ${file}`);
});
})
})
Promise.all(promises)
.then(() => console.log("all done"))
Since this is happening inside another then() you could just return Promise.all(promises) to handle it downstream.

Categories