I have a function declared that gets Firebase data async. The function have to wait until all the data is put inside an object. For some reason the function continues without waiting until the object has been set.
/** Get the data content */
const getData = async (widget, userId) => {
let promises = [];
let mainObject = {};
const pivotData = {};
const pivotName =
'user' + widget[1].type.charAt(0).toUpperCase() + widget[1].type.slice(1);
//Object entries
mainObject = {
default: widget[1],
};
mainObject['widgetId'] = widget[0];
//Main listner
const mainRef = firebase
.database()
.ref()
.child(widget[1].type);
//Pivot Listner
const pivotRef = firebase
.database()
.ref()
.child(pivotName)
.child(userId);
//Set promise
promises.push(
new Promise(async resolve => {
pivotRef
.once('value', snapPivot => {
snapPivot.forEach(function(result) {
if (result.val().widgetId === widget[0]) {
pivotData[result.key] = result.val();
mainObject['pivot'] = pivotData;
mainObject['typeId'] = result.key;
mainObject['main'] = {};
console.log('1');
mainRef.child(result.key).once('value', snapshot => {
console.log('2');
mainObject['main'][result.key] = snapshot.val();
});
}
});
})
.then(() => {
resolve();
console.log('3');
});
})
);
Promise.all(promises).then(results => {
return mainObject;
});
};
The expected outcome of the console.logs is 1,2,3 but it is showing 1,1,3,2
Why is the function not waiting on the .once function inside the loop?
The issue is that you are not waiting for the promises from mainRef.child(result.key).once() to resolve.
Additionally you are only pushing one promise to your promises array you want to be pushing the promise you get when calling mainRef.child(result.key).once().
Use await Promise.all() see MDN.
// Reference.once returns a promise Promise<DataSnapshot>
const dataSnapshot = await pivotRef.once('value');
let promises = [];
dataSnapshot.forEach((result) => {
const key = result.key;
const data = result.val();
if (data.widgetId === widget[0]) {
pivotData[key] = data;
mainObject['pivot'] = pivotData;
mainObject['typeId'] = key;
mainObject['main'] = {};
console.log('1');
const promise = mainRef.child(result.key)
.once('value', snapshot => {
console.log('2');
mainObject['main'][result.key] = snapshot.val();
});
// add promise to our promises array
promises.push(promise);
}
});
// wait for all the promises to be fulfilled (i.e resolved)
await Promise.all(promises);
// assuming 3 only needs to be logged once (at the end)
console.log('3');
console.log(mainObject);
return mainObject;
Let me know if this works!
Related
I have node JS api server and I'm having issues with correct chaining of the Promises:
app.post(
"/api/tasks",
async function (_req, res) {
const newArray = [{ MyTasks: [] }];
const getOne = async (owner, taskID) => {
return await getOneDocument(owner, taskID).then((result) => {
console.log("get one doc", result);
return result;
});
};
// first promise
let toApproveTasks = await getToApproveTasks(_req.body.userID);
console.log("1", toApproveTasks);
// loop trough the result of 1st promise and run async function for each
const arrayToDoc = async (array) => {
array.TasksToApprove.forEach(async (element) => {
let objToPush = await getOne(element.Owner, element.TaskID);
console.log("1.5", objToPush);
newArray.MyTasks.push(objToPush);
});
};
// second promise
await arrayToDoc(toApproveTasks);
console.log("2", newArray);
// third promise
let finalResult = await parseCosmosOutput(newArray);
console.log("3", finalResult);
res.status(200).send(finalResult);
}
);
What I get in console is :
1 [Object] - all good
Emppty Array
Empty Array
get one doc {object} - all good
1.5 {object} - all good
How would I make sure when I loop over result of 1st promise my code awaits async function and pushes to newArray results ?
Use For..of instead of forEach inside arrayToDoc function
E.g
const arrayToDoc = async (array) => {
for(let element of array.TasksToApprove){
let objToPush = await getOne(element.Owner, element.TaskID);
console.log("1.5", objToPush);
newArray.MyTasks.push(objToPush);
}
};
Trying to write this in a way that it will wait for the db operations to complete. Possible?
function addFriendsToSession(session, sessionId) {
const friends = [];
const incoming = session.users.forEach(async user => {
console.log('user', user);
await db
.collection('users/' + user + '/settings')
.doc(user)
.get()
.then(doc => {
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
});
friends.push(incoming);
return friends;
}
Use Promise.all.
Promise.all accepts an array of promises, and resolves once each promise has resolved. You can map your operation using map. E.g.,
const promises = session.users.map(user => {
console.log('user', user);
return db
.collection('users/' + user + '/settings')
.doc(user)
.get()
.then(doc => {
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
});
const friends = await Promise.all(promises)
return friends;
There are a number of issues here.
In db.then(), return is used, but this value is never returned from the function that encloses it (which is async user => {...})
const incoming is assigned the result of session.users.forEach, but Array.forEach() never has a return value (you may be thinking of Array.map()?)
Even if you solved the first two problems, incoming would still be an array of Promises
Additional suggestions:
Don't mix async/await with .then
Putting it all together:
const incoming = session.users.map(async user => {
console.log('user', user);
const doc = await db
.collection('users/' + user + '/settings')
.doc(user)
.get();
//we assigned `doc` using await instead of using .then
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
//incoming is not an array of Promises
const incomingFriends = await Promise.all(incoming); //takes an array of Promises and returns an array of the resolved values
//incomingFriends is now an array of friends
//next line would make the last element an array
//friends.push(incoming);
//you probably want to push every element of the array
friends.push(...incomingFriends);
I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.
Im trying to return a value from a Promise in async-await form and use it in another function in another file, but I do have problem because my Promise doesnt return any value.
When im trying to console.log('website') it returns me undefined immediately (it's like the value is not being fetched at all from API services). I dont know what im doing wrong, I really love to learn about Promises and Async-Await but each time im trying to work with them im getting more confused.
const dns = require('dns')
const iplocation = require("iplocation").default;
const emojiFlags = require('emoji-flags');
const getServerIPAddress = async (server) => {
return new Promise((resolve, reject) => {
dns.lookup(server, (err, address) => {
if (err) throw reject(err);
resolve(address);
});
});
};
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
iplocation(ip).then((res) => {
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
})
.catch(err => {
return `Location: Unknown`
});
}
(async function() {
console.log(await getServerLocation('www.google.com'))
})()
module.exports = {
getServerLocation
}
It is really important for me to get result from this function first, then use its value in another function. I wish you could give me tips on how to do tasks asynchronously.
You're clearly using async so it's not apparent why you're using then as well. If you use then then you must return the promise as well in order to preserve the promise chain:
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
return iplocation(ip).then((res) => {
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
})
.catch(err => {
return `Location: Unknown`
});
}
Otherwise just async this:
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
let res = await iplocation(ip);
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
}
const getServerLocation = async (server) => {
const ip = await getServerIPAddress(server)
//you need to return
return iplocation(ip).then((res) => {
const country = emojiFlags.countryCode(res.countryCode)
const result = `Location: ${country.emoji} ${country.name}`
return result
})
.catch(err => {
return `Location: Unknown`
});
}
So my problem is that empty array is returned because reading data from Firebase is not finished yet. I am using then method but it still executes everything inside then before.
var usersList = [];
const ref = firebase.database().ref()
ref.child('users').once('value').then(snap => {
snap.forEach(childSnap => {
const key = childSnap.key
ref.child(`users/${key}/points`).on('value', function(snapper) {
var points = snapper.val()
usersList.push({uid: key, points: points})
})
})
}).then(function() {
console.log(usersList)
})
Using then() itself is not a magical solution. You need to return a promise from within the callback, which your code is not doing. In this case you only want the final then() to be invoked once all the users are loaded, so you need to return a promise that only resolves when done.
const ref = firebase.database().ref()
ref.child('users').once('value').then(snap => {
var promises = [];
snap.forEach(childSnap => {
const key = childSnap.key
promises.push(
ref.child(`users/${key}/points`).once('value')
);
});
return Promise.all(promises);
}).then(function(snapshots) {
return snapshots.map(snapper => {
var points = snapper.val()
return {uid: key, points: points};
})
}).then(function(usersList) {
console.log(usersList)
})