'promise' return empty array - node.js

I am trying to return Array of tokens stored in Firebase, and I am using 'promise'.
function getUsersTokens() {
let dbRef = db.ref('/system/users');
let result = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let tokens = [];
snap.forEach(child => {
if(child.Status != "occupied"){
helper.getToken(child.key,db).then(function(token){
tokens.push(token);
});
}
});
resolve(tokens);
}, (err) => {
reject(err);
});
});
return result;
}
and this is the 'getToken' method from the "helper" module.
exports.getToken=function(uid,db){
return db.ref(`/Tokens/${uid}`).once('value').then(function(result){
return result.val();
});
};
The problem is that every time I push token into the array it all works fine, but when exit getUsersTokens() the array gets empty.
thanks for the help.

The issue is that your result promise is resolving too early because the helper.getToken() is non-blocking, so your forEach will finish running before all of the getToken() calls have finished pushing their token into tokens.
To make things a little easier, you can split your result promise into two promises. The first promise will be in charge of getting snap. The second promise will be in charge of iterating through snap to produce an array of tokens:
function getUsersTokens() {
let dbRef = db.ref('/system/users');
let result = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
resolve(snap);
}, (err) => {
reject(err);
});
});
return result.then(snap => {
let prommiseArr = [];
snap.forEach(child => {
if(child.Status != "occupied"){
let p = helper.getToken(child.key,db);
promiseArr.push(p);
}
});
return Promise.all(promiseArr); // resolves to array of tokens
});
}
Promise.all takes in an array of promises, and resolves when all of those promises have also resolved. the promise returned by getUsersToken will ultimately contain an array of tokens, because each promise of promiseArr resolves to a token.

It happens because the promise is resolved with the token array before getToken() resolves itself. You see an empty array because your handler runs before the tokens arrive.
You need to wait on that before resolving. Like this:
function getUsersTokens() {
let dbRef = db.ref('/system/users');
return new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
const tokensPromise = snap
.filter(child => child.Status !== "occupied")
.map(child => helper.getToken(child.key, db));
resolve(Promise.all(tokensPromise));
});
});
}

Promise.all as pointed out by #André Werlang and #Christian Santos it perfect here is an example using reduce way
function getUsersTokens() {
let dbRef = db.ref('/system/users');
let result = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
snap.reduce((chain, child) => {
return chain.then(array => {
return helper.getToken(child.key,db).then(function(token){
return array.push(token);
});
});
}, Promise.resolve([])).then(tokens=>{
resolve(tokens);
});
}, (err) => {
reject(err);
});
});
return result;
}

Related

Handling nested promises

I am trying to reject a value inside a nested promise but it doesn't seem to actually reject it correctly. In the code below, when I get an error from promiseVariable, it doesn't reject with the error. In the promiseVariable.catch statement, I have reject(err). Shouldn't that reject with that error for the whole promise?
return new Promise((resolve, reject) => {
const user = anotherFunction();
if (!user) {
promiseVariable.then((data) => {
user = data;
}).catch((err) => {
reject(err)
})
}
resolve(user);
});
Because it will start the promiseVariable chain and jump over to resolve. In the .then, you should resolve there, or put an else.
return new Promise((resolve, reject) => {
const user = anotherFunction();
if (!user) {
promiseVariable
.then(resolve)
.catch(reject);
} else {
resolve(user);
}
});
It seems you're over-promising, if that's a word. You can just "await" for your other promise to complete.
Here, anotherFunction is a simulated Promise, where after 3 seconds it returns a valid object for your conditional if(!user).
You can try changing resolve({user: 1234}) with a false (or falsy) value to see its rejection.
function anotherFunction() {
return new Promise((resolve,reject) => {
setTimeout(() => resolve({user: 1234}), 3000)
});
}
async function mainFunction() {
const user = await anotherFunction();
return user || {error: 404};
}
mainFunction().then(result => console.log(result));

file write issue in Async/Await

Here I am trying to retrieve objects and push them into the array. For some reason there is only one record being pushed into the file when it should contain more objects. Can you help me out with this or let me know where I am going wrong? Here is my code:
exports.createjson = (req, res, next) => {
try {
var myPromise = () => {
// ...
};
var callMyPromise = async () => {
const responsearray = [];
var result = await myPromise();
return new Promise((resolve, reject) => {
result.forEach(element => {
NewsModel.findOne({ _id: element.newsId }).exec(
async (err, result) => {
if (err) {
throw err;
}
reportsModel
.findOne({
$and: [
{ userId: req.query.userId },
{ newsId: element.newsId }
]
})
.exec((err, newsResult) => {
if (err) {
throw err;
}
// console.log(newsResult);
var response = {
newsId: element.newsId,
title: result.title,
collection: result.group,
belivibalityIndex: element.belivibalityIndex,
priorknowledge: element.priorknowledge,
readingTime: element.readingTime,
userId: element.userId,
comment: element.comment,
report: newsResult !== null ? newsResult.feedback : null
};
// #all object pushed and displayed in console
responsearray.push(response);
console.log(response);
console.log(responsearray.length);
// let data = JSON.stringify(responsearray);
// #here is the issue // fs.writeFileSync("abc.json", data, null, null, flag = 'a');
return responsearray;
});
}
);
});
});
};
callMyPromise().then(function(responsearray) {
res.json(responsearray);
});
} catch (error) {
next(error);
}
};
You're not quite using Promises properly. For example, you create a Promise object but never call the resolve/reject functions. In the forEach loop you are calling functions that use callbacks and when that work is done you can resolve the promise you're wrapping it in.
Also you're calling res.json and writing the file (though it's commented out) while you're in the forEach loop. That means res.json will get called multiple times, which is not allowed. You can only have one response from an http request.
I restructured the code so that it collects each promise in an array of Promises then waits for all of them to resolve. Only after all of the work is done, we can write the file and call res.json to complete the http request.
exports.createjson = async (req, res, next) => {
const responsearray = [];
var elements = await myPromise();
var promises = []; // collect a bunch of promises to wait on
elements.forEach(element => {
// one promise per element that resolves when response is on the array
var promise = new Promise(function(resolve, reject) {
NewsModel.findOne({ _id: element.newsId }).exec((err, result) => {
if (err) { return reject(err); }
reportsModel
.findOne({
$and: [{ userId: req.query.userId }, { newsId: element.newsId }]
})
.exec((err, newsResult) => {
if (err) { return reject(err); }
var response = { /* response body */ };
responsearray.push(response);
console.log(response);
console.log(responsearray.length);
// complete the promise now that the response is on the array
return resolve();
});
});
});
// collect each promise in an array so we can wait for them all
promises.push(promise);
});
// wait for all the work to complete
await Promise.all(promises).catch(err => next(err));
// write the responsearray to a file as json
let data = JSON.stringify(responsearray);
fs.writeFileSync("abc.json", data);
return res.json(responsearray);
};
I also removed the try/catch block since the Promise allows you to use .catch in a cleaner way. It simplifies the nesting which makes it easier to read.
The key takeaway here is the general structure:
// get your array to work with
var array = await someFunction()
var manyPromises = []
var manyResults = []
// for each thing in the array create a promise
array.forEach( thing => {
manyPromises.push( new Promise((resolve,reject) => {
doSomething(thing, (err, result) => {
if (err) return reject(err);
// store the results in the array and resolve the promise
manyResults.push(result)
return resolve();
});
});
});
// wait for all promises in manyPromises to complete
await Promise.all(manyPromises).catch(err => return next(err));
// now the many promises are done and manyResponses are ready
saveResponsesToFile(JSON.stringify(manyResponses))
return res.json(manyReponses)

How to return value then-catch block

My Codes below;
I've a then-catch block. My responseArray is a global variable. i got response from functionName function; but i can't use result out of then block. How can i use then response out of block?
My Codes below;
I've a then-catch block. My responseArray is a global variable. i got response from functionName function; but i can't use result out of then block. How can i use then response out of block?
module.exports = {
foo1: function(param){
return new Promise((resolve,reject) => {
var result = //some code here
resolve(result);
});
},
foo2: function(param){
return new Promise((resolve,reject) => {
this.foo1('abc').then(function(res){
let response = {
'item':'ok',
'result':res.some_field
};
console.log(response); // its ok here.
responseArray.push(response); //its ok here too
}).catch(err =>{
console.log(err);
reject(err);
});
console.log(responseArray); //nothing in array here
resolve(responseArray);
});
}
};
First thing to remember is that promises are asynchronous. Promises are doing exactly what they say, you are essentially signing a contract (promise) that you will get your data (or error) but not synchronously, but at some time in the future when the computations have finished.
In order to access your responseArray you will need to resolve your foo2 promise (inside of .then) and continue the promise chain by calling it, i.e.
module.exports = {
foo1: function(param){
return new Promise((resolve,reject) => {
var result = //some code here
resolve(result);
});
},
foo2: function(param){
return new Promise((resolve,reject) => {
this.foo1('abc').then(function(res){
let response = {
'item':'ok',
'result':res.some_field
};
console.log(response); // its ok here.
responseArray.push(response); //its ok here too
resolve(responseArray) // resolve the promise inside of .then
}).catch(err =>{
console.log(err);
reject(err);
});
});
}
};
foo2('someValue').then(response => {
console.log(response) // this will be your array
})
Also, as a side note, ensure you are not falling into the trap of the promise constructor anti-pattern. This is where you unnecessarily turn synchronous code into asynchronous code just for the sake of using "promises"
For example, a valid use of a promise would be to convert a callback, like so:
const getFile = filename => {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
whereas this is unnecessary:
const printData = data => {
return new Promise((resolve, reject) => {
resolve(console.log(data))
})
}
vs
const printData = data => {
console.log(data)
}
Read more here: What is the explicit promise construction antipattern and how do I avoid it?

Stream Promise results to client as they resolve in Promise.all

In Node, I am using bluebird's Promise.all to execute Promises asynchronously. I don't want to wait for all Promises to resolve before I send results back to the client; rather, I'd like to stream the result of each Promise as soon as it resolves. Is this possible? My research indicates that it is not, but I thought it worth reaching out to the community.
Here is the code (with no attempt to implement streaming):
async bulkExecution(req, res) {
try {
const { assets } = req.body;
let thePromises = _.map(assets, (asset) => {
return onePromise(asset);
});
// I want to stream each Promise result to client as it resolves
let results = await Promise.all(thePromises);
return res.status(200).send(results);
} catch (err) {
return res.status(500).send(err);
}
}
I think you'd have to iterate over all the promises and do a .then() on each promise and write a partial response then close the response after all the promises complete. Something like this should work. The order of the responses may be different than the order of the promise array. For instance, my example below will return the second promise first.
let res = {
write: (data) => {console.log(data)},
end: () => {}
};
let promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 500);
});
let promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
let promises = [promise1, promise2];
let count = 0;
promises.forEach((promise) => {
promise.then((data) => {
res.write(data);
count++;
if (count >= promises.length){
res.end();
}
})
})
Modified to write in JSON format.
let res = {
write: (data) => {
console.log(data)
},
end: () => {}
};
let promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({foo:"bar"});
}, 500);
});
let promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({bar:"foo"});
}, 300);
});
let promises = [promise1, promise2];
let count = 0;
res.write("[");
promises.forEach((promise) => {
promise.then((data) => {
res.write(JSON.stringify(data));
count++;
if (count >= promises.length) {
res.write("]");
res.end();
} else {
res.write(",");
}
})
})
Streaming out JSON like this isn't common, but could be used to reduce the amount of memory the server uses. That's because you don't need to create the full response in memory before sending the response. The client will still need enough memory to read the entire object into memory.
Try doing .then( data => data.json) on the promise like so
let thePromises = _.map(assets, (asset) => {
return onePromise(asset).then(data => data.json);
});

Get data after map function

not able to get items. it return [] . but it show correct on console.log(item). i think before my map() runs complete. it print all data. how to solve this issue. i am new in node.
function getBlockUsers() {
return new Promise(function (resolve, reject) {
BlockUser.find({userId:req.user._id}).populate("blockedId").lean().exec(function (err,result) {
if(err){
reject({"msg":"failed to getting block user."})
}else{
var results = [];
result.map(function(item){
Vehicle.findOne({userId:item.blockedId}).lean().exec(function(err,vehicle){
if(vehicle){
item.vehicleId = vehicle._id;
item.vehicleModel = vehicle.model;
}
results.push(item)
console.log(item)
});
});
resolve(results);
}
})
});
}
Because you use an async function in the map function wish is synchronous you need to create an array of promise and use Promise.all before the resolve to wait for all the results.
The code bellow should fix your issue.
function getBlockUsers() {
return new Promise(function (resolve, reject) {
BlockUser.find({userId:req.user._id}).populate("blockedId").lean().exec(function (err,result) {
if(err){
reject({"msg":"failed to getting block user."})
}else{
var results = result.map(function(item){
// don't forget to return in the map function
return new Promise(function (resolve1, reject1) {
Vehicle.findOne({userId:item.blockedId}).lean().exec(function(err,vehicle){
if (err) return reject1(err)
if(vehicle) {
item.vehicleId = vehicle._id;
item.vehicleModel = vehicle.model;
}
resolve1(item)
});
})
});
// here you wait all the promises of results
resolve(Promise.all(results));
}
})
});
}
The problem is you have non-blocking code inside your result.map().
You should try using just one DB query. Then resolve all the items in the exec callback. Otherwise use a promise for the original query.
Vehicle.find({ $in: { userId: result.map( item => item.blockedId) }}).lean().exec( (err, results) => {
// add the vehicle / model ids to each item in results
resolve(results)
})

Resources