Struggling to setup this function using async and await.
I have a function which maps over an object array. For each object, we make some external axios requests, receive a response before outputting an update object. I need to map over each object, update and return an array of updated objects.
Problem I am having, is that the output is not waiting for the resolved promise, resulting in undefined values being pushed to my array.
I note the array is also being logged before we handle each of the objects.
The following two functions are in question:
To build the object:
const createHRObj = async function (workflow) {
if (workflow.type_of_leave === "Personal") {
getAbsenceDetail(
`${workflow.employeeID}`,
`${workflow.startDate}`,
`${workflow.endDate}`
).then((res) => {
try {
console.log(`------- Handling Personal Leave! ----------`);
console.log(workflow);
if (res.Status != 0)
throw new Error(`PeopleHR absence status code: ${res.Status}`);
let absences = res.Result;
if (absences.length === 0) workflow.on_PeopleHR = false;
if (absences.length > 0) {
let count = 0;
absences.forEach((absence) => {
if (
Date.parse(absence.StartDate) <=
Date.parse(workflow.startDate) &&
Date.parse(absence.EndDate) >= Date.parse(workflow.endDate)
)
count++;
});
count > 0
? (workflow.on_PeopleHR = true)
: (workflow.on_PeopleHR = false);
}
console.log(`------- Absence Workflow Output! ----------`);
console.log(workflow);
console.log(
`------- Updating and returning workflow! ----------`
);
return workflow;
// console.log(`------- output from checkedarray ----------`);
// console.log(checkedArray)
} catch (err) {
console.log(err);
}
});
// Else, check for holiday
} else {
getHolidayDetail(
`${workflow.employeeID}`,
`${workflow.startDate}`,
`${workflow.endDate}`
).then((res) => {
try {
console.log(`------- Handling Business Leave! ----------`);
console.log(workflow);
if (res.data.Status != 10)
throw new Error(
`PeopleHR holiday status code: ${res.data.Status}`
);
let holidays = res.data.Result;
if (holidays.length === 0) workflow.on_PeopleHR = false;
if (holidays.length > 0) {
let count = 0;
holidays.forEach((holiday) => {
if (
Date.parse(holiday.StartDate) <=
Date.parse(workflow.startDate) &&
Date.parse(holiday.EndDate) >= Date.parse(workflow.endDate)
)
count++;
});
count > 0
? (workflow.on_PeopleHR = true)
: (workflow.on_PeopleHR = false);
}
console.log(`------- Absence Workflow Output! ----------`);
console.log(workflow);
console.log(
`------- Updating and returning workflow! ----------`
);
return workflow;
// console.log(`------- output from checkedarray ----------`);
// console.log(checkedArray)
} catch (err) {
console.log(err);
}
});
}
};
The second function, which calls the above function to handle the object creation is as follows:
const peopleHR_Check = async function (workflows) {
console.log(`----- We're now inside the PeopleHR Checker -------`);
Promise.all(
workflows.map(async (workflow) => {
let outputObj = await createHRObj(workflow)
return outputObj
})
)
.then(res => console.log(res))
};
You need to return the Promise from the async function createHRObj:
return getAbsenceDetail(...).then(...)
However, it is simpler to use await since it is an async function for more linear flow.
const res = await getAbsenceDetail(...);
// use res (move the code in .then() here)
// return the final output at the end
Related
When the code below runs I expect score to have a value (lets say: {"A": 1, "B": 2}), but when I print it I get an empty dict ({}).
I have tried to use promises but the result is the same.
driversBySeason(season) {
var query = `SELECT raceId FROM races WHERE year = ${season}`;
var score = {};
this.con.query(query, (err, drop) => {
if (err) {
console.error(err);
}
drop.forEach((element) => {
var raceId = element["raceId"];
query = `SELECT driverId, points FROM results WHERE raceId = ${raceId}`;
this.con.query(query, (err, drop) => {
if (err) {
console.error(err);
}
drop.forEach((element) => {
if (score[element["driverId"]] == undefined) {
score[element["driverId"]] = 0;
} else if (score[element["points"]] != undefined) {
score[element["driverId"]] += element["points"];
}
});
});
});
console.log(score);
});
}
First of all you need to change your .forEach loop to for const of loop or just a regular for loop because it just fires the code inside the callback and never waits for it. And then you need to change your this.con.query(...) function which has callback to promises too. Your code should be like this:
const asyncQuery = (query) => new Promise((resolve, reject) => {
this.con.query(query, (err, drop) => {
if (!!err) {
console.log(error);
return reject(err)
}
return resolve(drop);
})
});
async function driversBySeason(season) {
var query = `SELECT raceId FROM races WHERE year = ${season}`;
var score = {};
const drop = await asyncQuery(query).catch(err => err /* some error handling */);
for (const element of drop) {
var raceId = element["raceId"];
query = `SELECT driverId, points FROM results WHERE raceId = ${raceId}`;
const drop2 = await asyncQuery(query).catch(err => err /* some error handling */);
drop2.forEach((element) => {
if (score[element["driverId"]] == undefined) {
score[element["driverId"]] = 0;
} else if (score[element["points"]] != undefined) {
score[element["driverId"]] += element["points"];
}
});
}
console.log(score);
}
You are outside of your callback. When this executes the code continues to run past "this.con.query". You are seeing your empty object that was assigned at the top do to this. Go inside the callback after the drop.forEach where you assign the values, or convert to an async/await approach.
when you call driversBySeason your score variable has value of {}
and that is value for console.log() when you call it because of how closers are works and you updating score with callback function that happen later in time...
you get better understanding if you use promise...not callback
I have used await keyword in the main function to wait for the completion of async function call to poll() and yet the function call to my_plot is made before the completion of the poll() function.
async function main() {
getParametersData()
await poll()
my_plot()
}
async function getData() {
const response = await fetch(API)
const message = await response.json()
return message
}
async function poll(count = 1) {
console.log(`Polling ${count}`);
try {
const data = await getData();
if (data && Object.keys(data).length !== 0) {
console.log("Poll", data)
return;
} else {
setTimeout(poll, 5000, ++count);
}
}
catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
setTimeout(poll, 5000, 1);
}
}
async function my_plot() {
console.log("my plot")
}
Code output:
Polling 1
my plot
Polling 2
Polling 3
Poll [1,2,3]
Expected:
Polling 1
Polling 2
Polling 3
Poll [1,2,3]
my plot
Don't use setTimeout directly from within an async function. Instead, use a Promise-based wrapper.
It's surprising that modern ECMAScript doesn't come with an in-box Promise-based version of setTimeout, but it's straightforward to implement:
function delay( timeout ) {
if( typeof timeout !== 'number' || timeout < 0 ) throw new Error( "Timeout must be a non-negative integer milliseconds delay value." );
return new Promise( function( resolve ) {
setTimeout( resolve, timeout );
});
}
Then you can rewrite your poll function with a "real" while loop, like so (below).
I think your poll function should return a true/false value to indicate success or failure to the caller, if you ever need to.
Consider using typeof instead of less safe checks like Object.keys(data).length - or at least using a typeof check before using Object.keys.
Though annoyingly typeof null === 'object', so you will always need a !== null check, grumble...
As an alternative, consider having your own type-guard function (yes, I know this isn't TypeScript), that way you get even stronger guarantees that data contains what you need (as JS does not have static type checking).
async function poll( count = 1 ) {
console.log(`Polling ${count}`);
let i = 0;
do {
try {
const data = await getData();
if( isMyData( data ) ) {
return true;
}
}
catch( err ) {
console.error( err );
}
console.log( "Polling again in 5 seconds." );
await delay( 5000 );
i++;
}
while( i < count );
console.log( `Gave up after ${count} attempts.` );
return false;
}
// Type-guard:
function isMyData( data ) {
return (
( typeof data === 'object' )
&&
( data !== null )
&&
( 'this is my object' in data )
&&
( data['there are many like it but this one is mine'] )
&&
( data.myJavaScriptEngineIsMyBestFriend )
&&
data.itIsMyLife
&&
data.withoutMe_javaScriptIsUseless
&&
data.withoutJavaScript_iAmUseLess > 0
);
}
Note that if you intend to catch errors thrown by getData you should use a minimally scoped try instead of having more logic in there, as generally you won't want to catch unrelated errors.
Using the answer from How to make a promise from setTimeout, you can use a traditional loop.
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
async function poll() {
for (let count = 1;; count++) {
console.log(`Polling ${count}`);
try {
const data = Math.random(); // simulated data
if (data < 0.2) { // simulated 20% success rate
console.log("Poll", data)
return data;
} else {
console.log("Retrying in 5 seconds");
await later(5000);
}
} catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
count = 1;
await later(5000);
}
}
}
async function main() {
console.log("Start");
await poll();
console.log("Poll done");
}
main();
I have my function whose job is to go over a number of files (that use the values from the array as building blocks for file names) and download them using a reduce. It's more of a hack as of now but the Promise logic should work. Except it doesn.t
Here's my code:
function import_demo_files(data) {
/**
* Make a local copy of the passed data.
*/
let request_data = $.extend({}, data);
const get_number_of_files_1 = Promise.resolve({
'data' : {
'number_of_files' : 2
}
});
return new Promise((resolve, reject) => {
let import_files = get_number_of_files_1.then(function(response) {
new Array(response.data.number_of_files).fill(request_data.step_name).reduce((previous_promise, next_step_identifier) => {
let file_counter = 1;
return previous_promise.then((response) => {
if( response !== undefined ) {
if('finished_import' in response.data && response.data.finished_import === true || response.success === false) {
return import_files;
}
}
const recursively_install_step_file = () => import_demo_file({
demo_handle: request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': file_counter
}).call().then(function(response) {
file_counter++;
if('file_counter' in response.data && 'needs_resume' in response.data) {
if(response.data.needs_resume === true) {
file_counter = response.data.file_counter;
}
}
return response.data.keep_importing_more_files === true ? recursively_install_step_file() : response
});
return recursively_install_step_file();
}).catch(function(error) {
reject(error);
});
}, Promise.resolve())
}).catch(function(error) {
reject(error);
});
resolve(import_files);
});
}
Now, when I do:
const import_call = import_demo_files({ 'demo_handle' : 'demo-2', 'step_name' : 'post' });
console.log(import_call);
The console.log gives me back that import_call is, in fact a promise and it's resolved. I very much like the way return allows me to bail out of a promise-chain, but I have no idea how to properly resolve my promise chain in there, so clearly, it's marked as resolved when it isn't.
I would like to do import_call.then(... but that doesn't work as of now, it executes this code in here before it's actually done because of the improper handling in import_demo_files.
An asynchronous recursion inside a reduction isn't the simplest of things to cut your teeth on, and it's not immediately obvious why you would want to given that each iteration of the recursion is identical to every other iteration.
The reduce/recurse pattern is simpler to understand with the following pulled out, as outer members :
1. the `recursively_install_step_file()` function
1. the `new Array(...).fill(...)`, as `starterArray`
1. the object passed repeatedly to `import_demo_file()`, as `importOptions`)
This approach obviates the need for the variable file_counter, since importOptions.file_counter can be updated directly.
function import_demo_files(data) {
// outer members
let request_data = $.extend({}, data);
const importOptions = {
'demo_handle': request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': 1
};
const starterArray = new Array(2).fill(request_data.step_name);
function recursively_install_step_file() {
return import_demo_file(importOptions).then((res) => {
if('file_counter' in res.data && 'needs_resume' in res.data && res.data.needs_resume) {
importOptions.file_counter = res.data.file_counter; // should = be += ?
} else {
importOptions.file_counter++;
}
return res.data.keep_importing_more_files ? recursively_install_step_file() : res;
});
}
// the reduce/recurse pattern
return starterArray.reduce((previous_promise, next_step_identifier) => { // next_step_identifier is not used?
let importOptions.file_counter = 1; // reset to 1 at each stage of the reduction?
return previous_promise.then(response => {
if(response && ('finished_import' in response.data && response.data.finished_import || !response.success)) {
return response;
} else {
return recursively_install_step_file(); // execution will drop through to here on first iteration of the reduction
}
});
}, Promise.resolve());
}
May not be 100% correct but the overall pattern should be about right. Be prepared to work on it some.
I have a asynchronous code which I want to run synchronously in one of my node js script, But this doesn't wait for the code block to complete and resolves the empty object -
new Promise((resolve, reject) => {
if (object.email !== undefined) {
for (let i = 0; i <= object.email.length; i++) {
let emailObject = object.email[i]
if (emailObject !== undefined) {
this.isEmailUnsubscribed(emailObject, options).then(result => {
console.log('>> isEmailUnsubscribed result in send email notification: ' + result)
if (!result) {
emailObjects.push(emailObject.EmailID)
}
})
}
}
console.log('emailObjects')
console.log(emailObjects)
resolve(emailObjects)
}
}).then(emailObjects => {
object.email = emailObjects
console.log('Email Objects from rules.evaluate')
console.log(emailObjects) // At this point my object is always empty.
this.sendEmailToSelectedUsers(object, options)
})
This is because your loop is generating new promises that are resolved asycnoursly, use Promise.all when you need to run multiple promises:
For example:
if (object.email !== undefined) {
return Promise.all(object.email.map( emailObject => {
if(emailObject){
return this.isEmailUnsubscribed(emailObject, options)
}else{
return Promise.resolve()
}
} ))
.then(emailObjects => {
object.email = emailObjects
console.log('Email Objects from rules.evaluate')
console.log(emailObjects) // At this point my object is always empty.
this.sendEmailToSelectedUsers(object, options)
})
}
I need to iterate through an array and save every object to the Database.
At the end I need a callback with an array of all of the saved and failed object.
Below is the code I have:
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId')
db.List.create(element).then((list) => {
savedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
})
})
}
The code above works. Is there a way better to accomplish this?
I would recommend the following approach using Promise.all() to run the db.List.create() in parallel as it will return a Promise. By mapping the body array elements to Promises you can achieve better performance as they will run in parallel (and not have to track the complete count).
exports.addList = (app, body, callback) => {
var savedObjects = [];
var failedObjects = [];
Promise.all(
// map the array to return Promises
body.map(element => {
const list = _.pick(element, 'userAId','userBId');
return db.List.create(list)
.then(() => savedObjects.push(list))
.catch((error) => {
if (error.name === 'SequelizeUniqueConstraintError') {
failedObjects.push(list)
}
})
})
)
// when all Promises have resolved return the callback
.then(() => callback(savedObjects, failedObjects));
}
In your example your complete callback will always fire after the first promise is complete. This is because the create function is asynchronous while the surrounding loop is not, therefore the loop will have already completed by the time your first callback is triggered.
In your scenario this means element and index will always be the last in the loop. One way around this would be to move your promise chain into it's own function.
In this example I've used an extra flag to track the number of completed promises to trigger your complete method.
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
var complete = 0;
function createElement(element){
db.List.create(element).then((list) => {
savedObjects.push(element)
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
}
}).finally(() => {
complete++;
if(complete == body.length) {
callback(savedObjects, failedObjects)
}
});
}
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId');
createElement(element);
})
}