Node JS Promise then block executed before promise resolution - node.js

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).

Related

how to refer to return value of a function from outside in Node.js

I've been making dynamic dropdown box that each option has the table's name of BigQuery and I want to use return value (list) that is made inside .then method in function listTables(). However it seems not to work well . I'm new to Js so could you give any tips ?? Thank you so much.
function listTables() {
const {BigQuery} = require('#google-cloud/bigquery');
const bigquery = new BigQuery({
projectId: 'test_project',
});
const list = [];
bigquery
.dataset("test_table")
.getTables()
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);←I want to use this list outside a function
})
.catch(err => {
console.error('ERROR:', err);
});
}
listTables();
// select tag
let slt = document.getElementById("slt");
addTables(slt);
// return data
function getList() {
return new Promise(function (onFulliflled, onRejected) {
onFulliflled(list);
});
}
function addTables(slt) {
getList()
.then((list) => {
for (item of list) {
// optionを作成
let option = document.createElement("option");
option.text = item;
option.value = item;
// optionの追加
slt.appendChild(option);
}
})
.catch((err) => {
console.error("error", err);
});
}
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);
})
RESULT
[ 'test', 'test1', 'test2', 'test3' ]
You can do this in multiple ways.
Using calllback
function listTables(callback) {
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
callback(list);
})
//...
}
listTables(function(list){
});
Using promise or async/await
function listTables() {
return new Promise(function(resolve, reject){
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
resolve(list);
})
//...
});
}
// Promise
listTables().then(function(list){
});
//Async/await
var list = await listTables();
For the await to work you also need to run in within an async function. For example wrap it in async iife.
(async function(){
var list = await listTables();
})();
I don't use await myself so this is just from top of my head and might need some changes.

Async/await func in another async/await func in Node.js, MongoDB

I am trying to search available objects of a MongoDB collection(e.g. ParkingSpot) using specific business logic, the problem is that inside this function which is an async/await func I am looking inside another collection with .find() which I use as an async/await as well. The second function doesn't execute and gets back as an promise " Promise { } " and therefore the business logic is not applied. Can you help me please?
Here is 1st function:
exports.get_parkingSpots_avail_for_parking = async (req, res, next) => {
try {
const radius = req.body.radius;
const userLatitude = req.body.userLatitude;
const userLongitude = req.body.userLongitude;
const reqStartDate = new Date(req.body.reqStartDate);
const reqEndDate = new Date(req.body.reqEndDate);
const parkingSpots = await ParkingSpot.find({
isAvailable: true
}, (err, parkingSpots) => {
if (err) {
return res.status(500).json({
error: err
});
}
const freeParkingSpots = parkingSpots.filter(parkingSpot => {
return distanceCalculator(parkingSpot.latitude, parkingSpot.longitude, userLatitude, userLongitude) < radius
})
const availParkingSpots = freeParkingSpots.filter( freeParkingSpot => {
// console.log(freeParkingSpot._id);
console.log(isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id));
return isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id);
})
}).select('_id userId number address latitude longitude notes isAvailable startOfAvailability endOfAvailability');
console.log(parkingSpots);
if (parkingSpots.length > 0) {
return res.status(200).json({
parkingSpots: parkingSpots
});
}
return res.status(404).json({
message: "No available parking spots for requeste period"
});
} catch (err) {
res.status(500).json({
error: err
})
}
};
Second function which is being called :
module.exports = async function isParkingAvailable(reqStartDate, reqEndDate, parkingSpotId) {
const parkingSpotBookings = await Booking.find({ parkingSpotId: parkingSpotId})
.select('_id parkingSpotId sharerUserId renterUserId startDate endDate');
if (parkingSpotBookings.length == 0) {
return true;
}
parkingSpotBookings.filter(booking => {
//console.log(parkingSpotId);
//console.log("Is overlapping" +!isOverlapping(reqStartDate, reqEndDate, booking.startDate, booking.endDate));
return !isOverlapping(reqStartDate, reqEndDate, booking.startDate, booking.endDate)
})
}
So the problem is that calling second function appears as that in console.log :Promise { }
Await will Return the result to your parkingSpot variable.
For the second function:
You have defined this function as an async, that means it is holding asynchronous process, and in Node JS es6 async process is processed as a promise, so if you won't call it with await it will return a promise only
exports.get_parkingSpots_avail_for_parking = async (req, res, next) => {
try {
const radius = req.body.radius;
const userLatitude = req.body.userLatitude;
const userLongitude = req.body.userLongitude;
const reqStartDate = new Date(req.body.reqStartDate);
const reqEndDate = new Date(req.body.reqEndDate);
const parkingSpots = await ParkingSpot.find({isAvailable: true}).select('_id userId number address latitude longitude notes isAvailable startOfAvailability endOfAvailability');
const freeParkingSpots = parkingSpots.filter(parkingSpot => {
return distanceCalculator(parkingSpot.latitude, parkingSpot.longitude, userLatitude, userLongitude) < radius
});
const availParkingSpots = freeParkingSpots.filter( async freeParkingSpot => {
/* You have defined this function as an async, that means it is holding asynchronous process, and in
Node JS es6 async process is processed as a promise, so if you won't call it with await it will return a promise only */
return await isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id);
});
if (parkingSpots.length > 0) {
return res.status(200).json({
parkingSpots: parkingSpots
});
}
return res.status(404).json({
message: "No available parking spots for requeste period"
});
} catch (err) {
res.status(500).json({
error: err
})
}
};
I hope it will help you.
Thank you

Strange Promise.all behaviour

In one file called SearchBuilder.js i have this function ( wrote in this way as pure test )
self.validateInputs = async params => {
const validateDates = async () => {
const pick_up_date = moment(`${params.pick_up_date} ${params.pick_up_time}`),
drop_off_date = moment(`${params.drop_off_date} ${params.drop_off_date}`);
return true;
};
const validatePickUpId = async () => {
return true;
};
const validateDropOffId = async () => {
throw { 'code': 'NOT_FOUND'};
};
return Promise.all([
validateDates,
validatePickUpId,
validateDropOffId,
]);
};
In another file called searchEngine.js i have this:
self.run = async (type, search_inputs, account) => {
const searchBuilder = self.getSearchBuilder(type);
try {
const isValid = await searchBuilder.validateInputs(search_inputs);
console.log(isValid);
} catch (e) {
console.log('error input validation');
}
return search;
};
I erroneously thought that if any of the promises in Promise.all would fail, so my code will enter the catch block. Instead, even if the promise fails code inside the catch is never executed and inside isValid i get this.
[ [AsyncFunction: validateDates],
[AsyncFunction: validatePickUpId],
[AsyncFunction: validateDropOffId] ]

AWS Lambda nodejs - problem with return values from await async function

This is AWS Lambda function awaiting on async function. I can not get returned value. I need to do fetch data in loop as long as there are still some values on the server to be retrieved:
let do_search = true;
while (do_search) {
const url = 'xxx';
await fetchData(url)
.then(response => {
console.log("fetchData result:", response);
if (response) {
console.log("Stop searching, no more data");
do_search = false;
}
})
.....
while my fetchData returns false value when there is still more data to be processed. fetchData function:
async function fetchData(url) {
...
console.log("Returning false");
return false;
The problem is even my fetchData returns false,
my log are always:
Returning false
fetchData result: true
I have also tried to use other approach with:
const createModelBInstance = async () => {
let response = await fetchData(url)
console.log("fetchData result:", response);
if (response){
do_search=false;
}
}
await createModelBInstance();
Based on some examples on this forum.
But exactly same problem, my fetchData returns false while "fetchData result: true".
Any working example ? That Promise values returned are causing simple code to be very complicated :(
You shouldn't write hard loops in JavaScript. It's single-threaded.
Here's an example of a fetchData method that sleeps for 1 second, then indicates whether or not data is available (available with 10% likelihood, not available 90% likelihood). This is purely to simulate your asynchronous URL fetcher.
const getRandom = (low, count) => {
return Math.floor((Math.random() * count) + low)
}
const fetchData = () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const rand = getRandom(1, 10);
if (rand % 7) {
resolve({ rand });
} else {
resolve({ data: 'Here is the data', rand });
}
}, 1000);
});
return promise;
}
Here's an example of calling this code on an interval, every 2 seconds, until data becomes available.
const interval = setInterval(() => {
fetchData().then(rc => {
console.log(rc);
if (rc.data) {
// do something with rc.data
clearInterval(interval);
}
});
}, 2000);
An example of the output:
{ rand: 2 }
{ rand: 10 }
{ rand: 1 }
{ data: 'Here is the data', rand: 7 }

Return a value from function inside promise

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.

Resources