Querying multiple promises with a callback - javascript

In node.js i have a databaseMapper.js file, that uses the Ojai node MapR api. to extract data. So far i have it working with single documents, but since this is an async api, i have a bit of issues with querying multiple documents.
This is what i have so far:
function queryResultPromise(queryResult) {
//this should handle multiple promises
return new Promise((resolve, reject) => {
queryResult.on("data", resolve);
// ...presumably something here to hook an error event and call `reject`...
});
}
const getAllWithCondition = async (connectionString, tablename, condition) =>{
const connection = await ConnectionManager.getConnection(connectionString);
try {
const newStore = await connection.getStore(tablename);
const queryResult = await newStore.find(condition);
return await queryResultPromise(queryResult);
} finally {
connection.close();
}
}
here it will only return the first because queryResultPromise will resolve on the first document.. however the callback with "data" may occur multiple times, before the queryResult will end like this queryResult.on('end', () => connection.close())
i tried using something like Promise.all() to resolve all of them, but I'm not sure how i include the queryResult.on callback into this logic

This will work
const queryResultPromise = (queryResult) => {
return new Promise((resolve, reject) => {
let result = [];
queryResult.on('data', (data) => {
result.push(data)
});
queryResult.on('end', (data) => {
resolve(result);
});
queryResult.on('error', (err) => {
reject(err);
})
});
};

Related

Populate an array in the loop using promise

I have developed a code to fetch the image information of an object inside a loop statement. However, when I print the output at the bottom of the loop, it is empty. Can anyone help me with this, please? The getMediaInfo function is an Axios call.
const postsWithImageURLS = [];
res.data.forEach(async (post) => {
const response = await getMediaInfo(post.featured_media);
postsWithImageURLS.push({...post, featured_url: response});
});
console.log(postsWithImageURLS);
Promise.all(res.data.map(async (post) => {
if (post.categories.includes(NEWS_CATEGORY_ID)) {
const response = await getMediaInfo(post.featured_media);
post = {...post, featured_url: response};
return post;
}
})).then(postsWithImageURLS => console.log(postsWithImageURLS));
You should access postsWithImageURLS after all async methods finish.
I don't know exact content of the getMediaInfo function. But if it doesn't return a promise you can't use await before calling it.
Check this out:
const getMediaInfo = (data) => {
return new Promise(async (resolve, reject) => {
try {
let response = await axios.get(data); // data must be a url ofc
resolve(response); // Resolve must get a data you want to get when you call getMediaInfo
} catch (error) {
reject(error);
}
});
}
const postsWithImageURLS = [];
res.data.forEach(async (post) => {
const response = await getMediaInfo(post.featured_media);
postsWithImageURLS.push({...post, featured_url: response});
});
console.log(postsWithImageURLS);

chaining promises in functions

I have a small problem, how to create a promise chain in a sensible way so that the makeZip function will first add all the necessary files, then create the zip, and finally delete the previously added files? (The makeZip function also has to return a promise). In the example below I don't call deleteFile anywhere because I don't know exactly where to call it. when I tried to call it inside the add file function to delete the file immediately after adding it, for some unknown reason the console displayed the zip maked! log first and then file deleted.
const deleteFile = (file, result) => {
new Promise((resolve, reject) => {
fs.unlink(`./screenshots/${file}`, (err) => {
if (err) return reject(err);
console.log(`${file} deleted!`);
return resolve();
});
});
};
const addFile = (file) => {
new Promise((resolve, reject) => {
try {
zip.addLocalFile(`./screenshots/${file}`);
console.log(`${file} added`);
return resolve();
} catch {
return reject(new Error("failed to add file"));
}
});
};
const makeZip = () => {
Promise.all(fs.readdirSync("./screenshots").map((file) => addFile(file)))
.then(() => {
return new Promise((resolve, reject) => {
try {
zip.writeZip(`./zip_files/supername.zip`);
console.log("zip maked!");
resolve();
} catch {
return reject(new Error("failed making zip"));
}
});
})
.catch((err) => console.log(err));
};
the main cause of this is that you are not returning the promises you are instantiating in your function calls. Also I have some cool suggestion to make that can improve you code cleanliness.
[TIP]: Ever checked the promisify function in NodeJS util package, it comes with node and it is very convenient for converting functions that require callbacks as arguments into promise returning functions., I will demonstrate below anyhow.
// so I will work with one function because the problem resonates with the rest, so
// let us look at the add file function.
// so let us get the promisify function first
const promisify = require('util').promisify;
const addFile = (file) => {
// if addLocalFile is async then you can just return it
return zip.addLocalFile(`./screenshots/${file}`);
};
// okay so here is the promisify example, realized it wasn't applicable int the function
// above
const deleteFile = (file, result) => {
// so we will return here a. So because the function fs.unlink, takes a second arg that
// is a callback we can use promisify to convert the function into a promise
// returning function.
return promisify(fs.unlink)(`./screenshots/${file}`);
// so from there you can do your error handling.
};
So now let us put it all together in your last function, that is, makeZip
const makeZip = () => {
// good call on this, very interesting.
Promise.all(fs.readdirSync("./screenshots").map((file) => addFile(file)))
.then(() => {
return zip.writeZip(`./zip_files/supername.zip`);
})
.then(() => {
//... in here you can then unlink your files.
});
.catch((err) => console.log(err));
};
Everything should be good with these suggestions, hope it works out...
Thank you all for the hints, the solution turned out to be much simpler, just use the fs.unlinkSync method instead of the asynchronous fs.unlink.
const deleteFile = (file) => {
try {
fs.unlinkSync(`./screenshots/${file}`);
console.log(`${file} removed`);
} catch (err) {
console.error(err);
}
};
const addFile = (file) => {
try {
zip.addLocalFile(`./screenshots/${file}`);
console.log(`${file} added`);
deleteFile(file);
} catch (err) {
console.error(err);
}
};
const makeZip = () => {
fs.readdirSync("./screenshots").map((file) => addFile(file));
zip.writeZip(`./zip_files/supername.zip`);
console.log("zip maked!");
};

How do I access promise callback value outside of the function?

It is to my understanding that callback functions are asynchronous and cannot return a value like regular functions. Upon reading about promises, I thought I grasped a good understanding about them, that they are basically an enhanced version of callbacks that allows returning a value like asynchronous function. In my getConnections method, I am attempting to call the find() function on my database through mongoose, and I am attempting to grab this array of objects and send it to the views.
var test = new Promise((resolve, reject) => {
Database.find().then(list => {
resolve(list);
}).catch(err=> {
return reject(err);
})
})
console.log(test)
When I attempt to log to the console outside of the promise function, I get Promise { _U: 0, _V: 0, _W: null, _X: null }
I don't think this is functioning correctly, and I thought I utilized promises correctly. Could anyone point me in the right direction on how to return this array of objects outside of the callback function?
You can simply add await before the promise declaration.
i.e.
var test = await new Promise...
The thing is that when you write a function like this:
const getData = async () => { const response = await fetch(someUrl, {}, {}); return response;}
Then you also need to await that function when you call it. Like this:
const setData = async () => { const dataToSet = await getData(); }
let test = new Promise((resolve, reject) => {
Database.find().then(list => {
resolve(list);
}).catch(err=> {
return reject(err);
})
})
test
.then(result=>console.log(result))
Should solve your problem.
var someValue;
var test = await new Promise((resolve, reject) => {
Database.find().then(list => {
resolve(list);
}).catch(err=> {
return reject(err);
})
}).then(res => {
someValue=res;
})
console.log(someValue);

Functions are not waiting until they are resolved

I'm trying to execute functions one at a time, sequentially. Using promises, I believe it should work, but for me, it does not work. I've researched somewhat and found this question, and one of the answers explains to use Promises, that is what I've been trying to do.
Here's the functions:
async function loadCommands () {
return new Promise((resolve, reject) => {
let commands = 0;
readdir('./commands/', (error, files) => {
if (error) reject(error);
for (const file of files) {
if (!file.endsWith('.js')) return;
commands++;
}
}
resolve(commands); // this is in my code, I forgot to put it - sorry commenters
});
};
async function loadEvents () {
return new Promise(async (resolve, reject) => {
let events = 0;
readdir('./events/', (error, files) => {
if (error) reject (error);
for (const file of files) {
if (!file.endsWith('.js')) return;
events++
}
});
resolve(events);
});
};
I am then using await in an async function to try and make sure it each function resolves before going onto the next function:
console.log('started');
const events = await loadEvents();
console.log(events);
console.log('load commands');
const commands = await loadCommands();
console.log(commands);
console.log('end')
In the console, this is linked (keep in mind, I have no files in ./events/ and I have one file in ./commands/):
start
0 // expected
load commands
0 // not expected, it's supposed to be 1
end
What am I doing wrong? I want these functions to be run sequentially. I've tried making it so instead of functions, it's just the bare code in the one async function, but still came to the issue.
You never resolve() the promise that you create in loadCommands, and you resolve() the promise that you create in loadEvents before the readdir callback happened.
Also, don't do any logic in non-promise callbacks. Use the new Promise constructor only to promisify, and call only resolve/reject in the async callback:
function readdirPromise(path) {
return new Promise((resolve, reject) => {
readdir(path, (err, files) => {
if (err) reject(err);
else resolve(files);
});
});
});
or simply
import { promisify } from 'util';
const readdirPromise = promisify(readdir);
Then you can use that promise in your actual logic function:
async function countJsFiles(path) {
const files = await readdirPromise(path);
let count = 0;
for (const file of files) {
if (file.endsWith('.js'))
count++;
// I don't think you really wanted to `return` otherwise
}
return count;
}
function loadCommands() {
return countJsFiles('./commands/');
}
function loadEvents() {
return countJsFiles('./events/');
}
You're trying to use await outside async. You can await a promise only inside an async function. The functions returning promises ( here loadCommands & loadEvents ) don't need to be async. Make an async wrapper function like run and call the await statements inside it like this.
PS: Plus you also need to resolve loadCommands with commands in the callback itself. Same for loadEvents. Also, remove the return and simple increment the variable when true.
function loadCommands() {
return new Promise((resolve, reject) => {
let commands = 0;
readdir('./commands/', (error, files) => {
if (error) reject(error);
for (const file of files) {
if (file.endsWith('.js')) commands++;
}
}
resolve(commands);
});
};
function loadEvents() {
return new Promise((resolve, reject) => {
let events = 0;
readdir('./events/', (error, files) => {
if (error) reject(error);
for (const file of files) {
if (file.endsWith('.js')) events++
}
resolve(events);
});
});
};
async function run() {
console.log('started');
const events = await loadEvents();
console.log(events);
console.log('load commands');
const commands = await loadCommands();
console.log(commands);
console.log('end')
}
run();
Hope this helps !

How can I wait until the whole batch of requests are made and the promises are solved before I send the data on the callback function?

I have an object with 5 items, each of them will send an http request 3 times.
I save that in a
var promise
var promise1
var promise2
In the end, I am resolving (trying) the promises using
Promise.all([promise, promise1, promise2]]
And then I send the data to a callback function.
I am using array.map() to do my task on that array, all the requests and Promise.all are happening inside that.
How can I wait until the whole batch of requests are made and the promises are solved before I send the data on the callback function?
async function requestJahrStatistic(jahreStatistic, callback){
Promise.all(
jahreStatistic.map(async (item) => {
var periods = getReportingPeriod(item.period);
connection.statistic_id = item.statistic_id;
connection.reporting_period = periods.prevYear;
var promise = new Promise(function(resolve, reject) {
sendHTTPRequest(item, function(result) {
resolve(result);
});
});
connection.reporting_period = periods.actualYear;
var promise1 = new Promise(function(resolve, reject) {
sendHTTPRequest(item, function(result) {
resolve(result);
});
});
connection.reporting_period = periods.nextYear;
var promise2 = new Promise(function(resolve, reject) {
sendHTTPRequest(item, function(result) {
resolve(result);
});
});
Promise.all([promise, promise1, promise2]).then(async resolved => {
var res = await resolved
return res
});
})
).then(async resolved =>{
var resp = await resolved;
callback(resp)
});
}
This was the last thing I tried before writing the question
There are several problems with that code:
requestJahrStatistic shouldn't be async if it reports its results by calling a callback
You use this pattern in a couple of places:
.then(async resolved => {
var res = await resolved
return res
});
That serves no purpose (unless... see #5) and can be completely removed.
There's no reason for the map callback to be async, as you're not using await within it.
You're repeating your logic wrapping sendHTTPRequest in a promise, and failing to handle errors in it. Don't repeat yourself, make a function for that.
It looks like connection.statistic_id and connection.reporting_period are used by the HTTP requests somehow. They shouldn't be, that's spooky action at a distance. :-) But if they are, then none of this can be in parallel since you have to wait for a request using a given statistic_id and reporting_period to complete before you can start the next.
You're not handling errors.
If I assume connection.reporting_period is used by the HTTP requests, that means they can't overlap, so none of this can be in parallel and you can't use Promise.all for it. You'd need something like:
If connection.reporting_period isn't used by the HTTP requests, this can all be parallel:
function sendHTTPRequestP(item) {
return new Promise((resolve, reject) => {
sendHTTPRequest(item, result => {
if (/*an error occurred*/) {
reject(new Error(/*...*/));
} else {
resolve(result);
}
});
})
}
// Completely serial because of spooky action at a distance with
// `connection.statistic_id` and `connection.reporting_period`
function requestJahrStatistic(jahreStatistic, callback) {
Promise.resolve(async () => {
const results = [];
for (const item of jahreStatistic) {
const periods = getReportingPeriod(item.period);
connection.statistic_id = item.statistic_id;
connection.reporting_period = periods.prevYear;
const result1 = await sendHTTPRequestP(item);
connection.reporting_period = periods.actualYear;
const result2 = await sendHTTPRequestP(item);
connection.reporting_period = periods.nextYear;
const result3 = await sendHTTPRequestP(item);
results.push([result1, result2, result3]);
}
return results;
})
.then(callback)
.catch(error => {
// Handle/report error, call `callback` with the appropriate error flag
});
}
Or something along those lines. Note that what callback will receive is an array of arrays. The outer array will have as many entries as jahreStatistic; each of those entries will be an array of the results of the three HTTP calls.
If you can change things so that each operation takes arguments rather than spooky action at a distance (I see that sendHTTPRequest already has the item so can presumably get statistic_id from it, so we just have to pass period), you can make things parallel:
function sendHTTPRequestP(item, reporting_period) {
return new Promise((resolve, reject) => {
sendHTTPRequest(item, reporting_period, result => {
if (/*an error occurred*/) {
reject(new Error(/*...*/));
} else {
resolve(result);
}
});
})
}
function requestJahrStatistic(jahreStatistic, callback){
Promise.all(
jahreStatistic.map((item) => {
const periods = getReportingPeriod(item.period);
return Promise.all([
sendHTTPRequestP(item, periods.prevYear),
sendHTTPRequestP(item, periods.actualYear),
sendHTTPRequestP(item, periods.nextYear)
]);
})
)
.then(callback)
.catch(error => {
// Handle/report error, call `callback` with the appropriate error flag
});
}
Or something along those lines.

Categories

Resources