I am trying to return the array shiftInfo from the function csData() within a promise.
function crewsense(){
var request = CS.find({});
request
.then(result => {
var created = result[0].created,
currentTime = moment(),
diff = (currentTime - created);
if(diff < 84600000){
console.log("Current Token is Valid");
var access_token = result[0].access_token;
console.log('Obtaining Crewsense Shift Data');
return access_token
}else{
console.log("Current Token is invalid. Updating Token");
csToken();
}
}).then(access_token => {
csData(access_token) //I am trying to get this function to return async data.
}).then(shiftInfo => { //I want to use the data here.
})
Here is the csData function:
function csData(csKey) {
const dayURL = {
method: 'get',
url: 'https://api.crewsense.com/v1/schedule?start='+today+'%2007:30:00&end='+tomorrow+'%2007:30:00',
headers:{
Authorization: csKey,
}
}
const request = axios(dayURL)
request
.then(result => {
var shiftInfo = [];
var thisShift = [];
var onDuty = result.data.days[moment().format("YYYY-MM-DD")].assignments;
thisShift.push(result.data.days[moment().format("YYYY-MM-DD")].day_color);
var persons = [];
var i = 0;
for(var i=0; i<onDuty.length; i++){
let station = onDuty[i].name
for(var x=0; x<onDuty[i].shifts.length; x++){
var person = {
name: onDuty[i].shifts[x].user.name,
position: onDuty[i].shifts[x].qualifiers[0].name,
station: station
}
persons.push(person);
}
}
shiftInfo = [{thisShift}, {persons}];
// console.log(shiftInfo)
return shiftInfo
})
.catch(error => console.error('csData error:', error))
}
I have attempted assigning var shiftInfo = csData(access_token) w/o success and several other ways to call the csData function. I have attempted reading other like problems on here and I have just ended up confused. If someone can point me in the right direction or please point out the fix I might be able to get it to click in my head.
I appreciate everyone's time.
Thanks!
Whatever you return inside a then, will be passed to the next then callback. If you return a Promise, the result of the promise will be sent to the next then callback:
new Promise((resolve) => {
// We resolve to the value we want
resolve("yay");
}).then((value) => {
// In the first then, value will be "yay"
console.log("First then:", value);
// Then we return a new value "yay x2"
return value + " x2";
}).then((value) => {
// In this second then, we received "yay x2"
console.log("Second then:", value);
// Then we return a promise that will resolve to "yay x2 again"
return new Promise((resolve) => {
setTimeout(() => {
resolve(value + " again");
}, 1000);
});
}).then((value) => {
// After a second (when the returned Promise is resolved) we get the new value "yay x2 again"
console.log("Third then:", value);
// And now we return a Promise that will reject
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("wtf"));
}, 1000);
});
}).catch((error) => {
// This catch on the whole promise chain will catch any promise rejected
console.log(error.toString());
});
So simply csData must return the promise is creating, and you need to return that promise to the then callback you want:
[...]
}).then(access_token => {
return csData(access_token) //I am trying to get this function to return async data.
}).then(shiftInfo => { //I want to use the data here.
console.log(shiftInfo);
}).catch((err) => {
// Whatever...
});
function csData(csKey) {
[...]
return request.then(result => {
[...]
}
Because you are returning a promise, I recommend you to add the catch outside csData and add it to the promise chain you have before.
Related
I have a code to fetch directory names from first API. For every directory, need to get the file name from a second API. I am using something like this in my Node JS code -
async function main_function(req, res) {
const response = await fetch(...)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return "";
}
})
.then((data) => {
dirs = ...some logic to extract number of directories...
const tempPromises = [];
for (i = 0; i < dirs.length; i++) {
tempPromises.push(getFilename(i));
}
console.log(tempPromises); // Prints [ Promise { <pending> } ]
Promise.all(tempPromises).then((result_new) => {
console.log(result_new); // This prints "undefined"
res.send({ status: "ok" });
});
});
}
async function getFilename(inp_a) {
const response = await fetch(...)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return "";
}
})
.then((data) => {
return new Promise((resolve) => {
resolve("Temp Name");
});
});
}
What am I missing here?
Your getFilename() doesn't seem to be returning anything i.e it's returning undefined. Try returning response at the end of the function,
async function getFilename(inp_a) {
const response = ...
return response;
}
Thanks to Mat J for the comment. I was able to simplify my code and also learn when no to use chaining.
Also thanks to Shadab's answer which helped me know that async function always returns a promise and it was that default promise being returned and not the actual string. Wasn't aware of that. (I am pretty new to JS)
Here's my final code/logic which works -
async function main_function(req,res){
try{
const response = await fetch(...)
const resp = await response.text();
dirs = ...some logic to extract number of directories...
const tempPromises = [];
for (i = 0; i < dirs.length; i++) {
tempPromises.push(getFilename(i));
}
Promise.all(tempPromises).then((result_new) => {
console.log(result_new);
res.send({ status: "ok" });
});
}
catch(err){
console.log(err)
res.send({"status" : "error"})
}
}
async function getFilename(inp_a) {
const response = await fetch(...)
respText = await response.text();
return("Temp Name"); //
}
Simple code path, that is better explained with bullet points
API call
Call to DB
Result is a list of objects
Inside the then block make a DB call for each object to hydrate children
Inside another then block res.send(hydrated object)
Problem
Step 5 happens before step 4 completes (Code below)
//api endpoint
router.post('/get-data', getObjects);
export const getObjects: express.RequestHandler = (req, res) => {
queryContainer(containerId, querySpec)
.then((result) => {
return getChildren(result, req.body.criteria);
})
.then((result) => {
res.send(result);
});
}
export async function queryContainer(containerId, querySpec) {
const { result: results } = await client.database(databaseId).container(containerId).items.query(querySpec, {enableCrossPartitionQuery: true}).toArray()
.catch( (error) => {
console.log("Error! ", error);
});
return results;
}
function getChildren(result: any, criteria: any) {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
results.then(children => {
result.children.push(...children)
return result;
});
}
return result;
}
export const dbCallToGetChildren = (async function (username) {
const querySpec = {
query: "SELECT * FROM root r WHERE r.userName=#userName",
parameters: [
{name: "#userName", value: username}
]
};
queryContainer(containerId, querySpec)
.then((results) => {
return results;
})
.catch((error) => {
console.log("Error " + error);
return Promise.resolve;
});
});
I have a few comments about your code:
you are doing mutation which is a bad practice, getChildren function accepts result type any and then it makes some changes to it like
result.children = []
Avoid using any and try to define a type
in your code because you are doing mutation so you do not even need to return back the result because the original object is already changed but as I mentioned earlier you should avoid mutation.
The main problem that step 5 executes before step 4 is that getChildren won't return back promise, I made some changes in your code to accommodate promise.
function getChildren(result: any, criteria: any): Promise<any> {
return new Promise((resolve, reject) => {
if (criteria) {
result.children = []
const actions = result
.map(item => item.element)
.map(dbCallToGetChildren)
Promise.all(actions).then(children => { // returns a promise
result.children.push(...children)
return resolve("successfully is processed!")
})
}
reject("invalid criteria!")
})
}
Step 5 happens before step 4 (getChildren function) completes because getChildren isn't returning a Promise. Changing it to the following might fix the problem:
function getChildren(result: any, criteria: any) {
return new Promise(resolve => {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
results.then(children => {
result.children.push(...children)
resolve(result);
});
} else {
resolve(result);
}
});
}
Inside results.then(children => { ... } there is now resolve(result); to ensure the return getChildren(result, req.body.criteria); statement within the queryContainer call doesn't complete until the Promise resolves.
In order for step 4 to fully complete before step 5 is executed, we need to promisify every code-path that goes through getChildren.
That means we should change this:
function getChildren(result: any, criteria: any) {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
results.then(children => {
result.children.push(...children)
return result;
});
}
return result;
}
Into the following (pay attention to the code comments):
function getChildren(result: any, criteria: any) {
if(criteria) {
result.children = [];
var actions = result
.map(result => result.element)
.map(dbCallToGetChildren);
var results = Promise.all(actions);
return results.then(children => { // returns a promise
result.children.push(...children)
return result;
});
}
return Promise.resolve(result); // returns a promise
}
It's important to return otherwise the code in this line is being executed in async manner (which doesn't guarantee that step 4 will complete before step 5 begins).
I need to wait for an async method (a call to my database) for every object in an array. Right now I have a for loop going through the array calling the async method on each object. The async function is successful but I need to wait for every async call to finish before moving on. Doing some research I have found that Promises combined with await or other methods are the solution to my problem but I haven't been able to figure it out. Here is my code I have right now.
Here is my class with the async function
Vacation : class Vacation {
constructor(id, destination, description, attendee_creator_id) {
this.id = id;
this.destination = destination;
this.description = description;
this.attendee_creator_id = attendee_creator_id;
this.creator = undefined;
this.votes = undefined;
}
find_creator(pool){
return new Promise(function (resolve, reject) {
var self = this;
var query = "SELECT * FROM vacation_attendee WHERE id = " + self.attendee_creator_id;
pool.query(query, function(error, result) {
if (error) {
console.log("Error in query for vacation creator " + error);
return reject(error);
}
var creator = new attendee.VacationAttendee(result.rows[0].full_name, result.rows[0].email, result.rows[0].password_hash);
self.creator = creator;
console.log("creator found ----> " + self.creator.full_name + "in " + self.destination);
resolve(true);
});
})
}
here is how i'm calling the async function
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
callback(error);
}
var all_complete = loop_through_vacations(vacations_query_result.rows, pool);
callback(null, all_complete);
});
}
async function loop_through_vacations(vacations_incomplete, pool) {
var all_vacations = [];
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
vacation_at_index.find_creator(pool)
.then(()=> {
all_vacations.push(vacation_at_index);
})
.catch(error=> {
console.log(error);
});
console.log("passed vacation " + vacation_at_index.destination);
}
return await Promise.all(all_vacations);
}
You do it in a wrong way, you don't wait anything in you for-loop.
return await Promise.all(all_vacations); does not work, because all_vacations is not a Promise array.
In your case, we have many way to do this, my way just is a example: Create a array to store all promises what have been created in your for-loop, then wait until the all promises finished by Promise.all syntax.
async function loop_through_vacations(vacations_incomplete, pool) {
var all_vacations = [];
var promises = []; // store all promise
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
promises.push( // push your promise to a store - promises
vacation_at_index.find_creator(pool)
.then(() => {
all_vacations.push(vacation_at_index)
})
.catch((err) => {
console.log(err); // Skip error???
})
);
}
await Promise.all(promises); // wait until all promises finished
return all_vacations;
}
I don't know why you use async/await mix with callback style, I recommend async/await for any case:
Mix style:
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, async function(error, vacations_query_result) { // async
if (error) {
console.log("error in vacations query " + error);
return callback(error); // return to break process
}
var all_complete = await loop_through_vacations(vacations_query_result.rows, pool); // await
callback(null, all_complete);
});
}
async/await:
async function get_all_vacations() {
var sql_vacations_query = "SELECT * FROM vacation";
var rows = await new Promise((resolve, reject) => {
pool.query(sql_vacations_query, function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
return reject(error);
}
resolve(vacations_query_result.rows);
});
})
var all_complete = await loop_through_vacations(rows, pool); // await
return all_complete;
}
For me, you can try to reformat your SQL queries, the code will be cleaner and it will take less time to make a big query once than multiple queries.
If I understand joins in your database:
SELECT *
FROM vacation_attendee AS attendee
INNER JOIN vacation ON vacation.attendee_creator_id = attendee.id
-- WHERECLAUSEYOUWANT
Then:
async function get_all_vacations(callback){
const allComplete = await makeThatBigQuery(); // + treat data as you want
callback(null, allComplete);
}
In your definition of get_all_vacations you don't wait all_complete, so the callback is called before all_complete is ready
This is how the await/promises part of the code should look like:
function get_all_vacations(callback) {
var sql_vacations_query = "SELECT * FROM vacation";
pool.query(sql_vacations_query, async function(error, vacations_query_result) {
if (error) {
console.log("error in vacations query " + error);
callback(error);
}
var all_complete = await loop_through_vacations(vacations_query_result.rows, pool);
callback(null, all_complete);
});
}
async function loop_through_vacations(vacations_incomplete, pool) {
const promises = [];
for (var vacation_data of vacations_incomplete) {
var vacation_at_index = new vac.Vacation(vacation_data.id, vacation_data.destination, vacation_data.description, vacation_data.attendee_creator_id);
promises.push(vacation_at_index.find_creator(pool));
}
return await Promise.all(promises);
}
Ideally you should be removing all the callbacks(not sure if asyn/await is supported for pool.query methods) and change the whole code to async/await style. Also you should be resolving the promise from 'find_creator' with resolve(creator), so that the value is obtained when the promise is resolved.
I want to send data back via functions.https.onCall, the code works and returns data to the console but i don't know why I can't send the data back to the device(in return number one, I understand can't why return number 2 is not working because the asynchronous task, so there is a way to to it after its finish?).
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
var posts= new Array();
var finalposts = new Array();
function post (uid,text,user_name,vid,sendtime,vote,tag,pic,lan){
this.uid=uid;
this.text=text;
this.user_name=user_name;
this.vid=vid;
this.sendtime=sendtime;
this.vote=vote;
this.tag=tag;
this.pic=pic;
this.lan=lan;
}
exports.getWarm = functions.https.onCall((data, context) => {
finalposts = [];
posts = [];
db.collection('local uploads/heb/posts').where('vote', '<', 200).where('vote', '>', 100).get()
.then((snapshot) => {
snapshot.forEach((doc) => {
posts.push( new post(doc.data()["uid"],doc.data()["text"],doc.data()["user_name"],doc.data()["vid"],
doc.data()["sendtime"],doc.data()["vote"],doc.data()["tag"],doc.data()["pic"],doc.data()["lan"]));
});
posts.sort(function(a,b) {return (a.sendtime<b.sendtime)? 1:((a.sendtime>b.sendtime)? -1:0);});
for (var i =0; i<data.refresh_num*2; i++) {
finalposts.push(posts[i]);
}
console.log('hey', '=>', finalposts);
// working: the posts are shown in the console
// (1.) return { posts: finalposts };
// returns null
})
.catch((err) => {
console.log('Error getting documents', err);
});
// (2)return { posts: finalposts };
// returns {posts=[]}
return { posts: 'data' };
// returns {posts=data}
});
You need to return a Promise from your Firebase function in order to return data. Try adding return before your db.collection and see if that works.
From the docs: https://firebase.google.com/docs/functions/callable
To return data after an asynchronous operation, return a promise.
EDIT: I am not very familiar with Firebase Firestore, but any calls that generate a promise, add a return statement before it.
Also, from my experience, you will want to move your return { posts: 'data' }; to execute after your last promise by using then.
well it was simpler then I thought, I just needed to return it.
exports.getWarm = functions.https.onCall((data, context) => {
finalposts = [];
posts = [];
return db.collection('local uploads/heb/posts').where('vote', '<', 200).where('vote', '>', 100).get()
.then((snapshot) => {
snapshot.forEach((doc) => {
posts.push( new post(doc.data()["uid"],doc.data()["text"],doc.data()["user_name"],doc.data()["vid"],
doc.data()["sendtime"],doc.data()["vote"],doc.data()["tag"],doc.data()["pic"],doc.data()["lan"]));
});
posts.sort(function(a,b) {return (a.sendtime<b.sendtime)? 1:((a.sendtime>b.sendtime)? -1:0);});
for (var i =0; i<data.refresh_num*2; i++) {
finalposts.push(posts[i]);
}
console.log('hey', '=>', finalposts);
return { posts: finalposts };
})
.catch((err) => {
console.log('Error getting documents', err);
});
});
I have code in one of my file like below:
this.xmlObjectRepositoryLoader = function (xmlPath){
var innerMap = {};
var elementName;
var filePath = xmlPath+'.xml'
var self = this
return new Promise(
function(resolve, reject){
console.log("In xmlObjectRepositoryLoader : "+filePath)
self.readFilePromisified(filePath)
.then(text => {
var doc = domparser.parseFromString(text,"text/xml");
var elements = doc.getElementsByTagName("Element");
for(var i =0 ; i< elements.length;i++){
var elm = elements[i];
elementName = elm.getAttribute("name");
var params = elm.getElementsByTagName("param");
innerMap = {};
for(var j =0 ; j< params.length;j++){
var param = params[j];
var locatorType = param.getAttribute("type");
var locatorValue = param.getAttribute("value");
innerMap[locatorType] = locatorValue;
}
map[elementName] = innerMap;
innerMap={};
}
console.log(map) // prints the map
resolve(text)
})
.catch(error => {
reject(error)
});
});
}
this.readFilePromisified = function(filename) {
console.log("In readFilePromisified : "+filename)
return new Promise(
function (resolve, reject) {
fs.readFile(filename, { encoding: 'utf8' },
(error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
})
})
}
I am calling above function from another file as below:
objectRepositoryLoader.readObjectRepository(fileName)
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
But it gives me error as
.then(text => { ^
TypeError: Cannot read property 'then' of undefined
In this case how can I use promise to call another promise function and then use the returned value in one more promise function and return calculated value to calling function where I can use the value in other functions. I sound a bit confused. Please help
Referrence link: Node.js : Call function using value from callback or async
Following is readObjectRepository definition :
readObjectRepository = function(fileName) {
var filePath = '../'+fileName;
xmlObjectRepositoryLoader(filePath)
}
I got the issue in your code. The method readObjectRepository doesn't return a promise and infact doesn't return anything
So you cannot chain .then
To do that- in the function definition of readObjectRepository return the promise of xmlObjectRepositoryLoader
Make this change and it should be all good
readObjectRepository = function(fileName) {
var filePath = '../'+fileName;
return xmlObjectRepositoryLoader(filePath) //This will return the promise of xmlObjectRepositoryLoader which you can handle in 'then' and obtain the text
}