Reputation: 2406
For a specific function, a list of tags (rfids) are required from a database, but various tables should be checked.
The issue is centered around the problem of a long running operation isn't await
ed upon to finish before proceeding to the next.
Problem:
The code called below runs as follows (# numbers correspond to number in code below):
getAllTags()
is called, starting the procawait db.dbRfid.findAll()
returns with X amount of valid results (not Promise
objects)const driverTags = await tag.getDrivers();
At this point, one would expect this function to return the result and proceed to the next function i.e.
// #5
const truckTags = await tag.getTrucks();
What infact happens is for each of the allTags
items, the getDrivers()
gets called and the filter()
exits and the code continues on by executing this next:
// #6
if (pureTags) {
return filteredTags;
}
Question:
To my understanding, I am await
ing async
operations correctly, yet it seems like the filter only accepts/allows one async
operation.
I assume I did something wrong, yet I am unable to the cause of the problem. Any advice would be appreciated!
Full code implementation below:
Called with e.g.
const listOfTags = await getAllTags();
Specific tag requirements (all valid, unused, enabled & unrevoked tags)
const listOfTags = await getAllTags(false, true, true, true);
Problem code:
// #1
const getAllTags = async (pureTags = false, unused = true, enabled = true, notRevoked = false) => {
// #2
let allTags = await db.dbRfid.findAll()
// #3
let filteredTags = await allTags.filter(async tag => {
// check tag used
if (unused) {
// #4
const driverTags = await tag.getDrivers();
// #5
const truckTags = await tag.getTrucks();
const userTags = await tag.getUsers();
if (driverTags.length > 0) {
return false;
}
if (truckTags.length > 0) {
return false;
}
if (userTags.length > 0) {
return false;
}
}
// check tag enabled
if (enabled && !tag.enabled) {
return false;
}
// check tag revoked or return true
return notRevoked && !tag.revoked;
});
// return tags as is
// #6
if (pureTags) {
return filteredTags;
}
return filteredTags.map(tag => {
return {
id: tag.id,
rfid: tag.rfid,
expiryDate: tag.expiryDate,
revoked: tag.revoked,
enabled: tag.enabled
}
});
}
Update
I should mention:
.getDrivers()
is a getter created by sequelize which returns a promise.Update 2
After comment by evgeni fotia
I originally had this code, however chose not to include it as it may havve complicated matters. However, this it the original code I attempted with the addition of the await Promise.all()
.
const getAllTags = async (pureTags = false, unused = true, enabled = true, notRevoked = false) => {
let allTags = await db.dbRfid.findAll()
let filteredTags = await Promise.all(
// await allTags.filter(async tag => { //tried the await here too - for good measure
allTags.filter(async tag => {
// check tag used
if (unused) {
const driverTags = await tag.getDrivers();
const truckTags = await tag.getTrucks();
const userTags = await tag.getUsers();
if (driverTags.length > 0) {
return false;
}
if (truckTags.length > 0) {
return false;
}
if (userTags.length > 0) {
return false;
}
}
// check tag enabled
if (enabled && !tag.enabled) {
return false;
}
// check tag revoked or return true
return notRevoked && !tag.revoked;
})
);
// return tags as is
if (pureTags) {
return filteredTags;
}
return filteredTags.map(tag => {
return {
id: tag.id,
rfid: tag.rfid,
expiryDate: tag.expiryDate,
revoked: tag.revoked,
enabled: tag.enabled
}
});
}
Please note, after running the code in this update or in the original question, I see the debugger hitting the getDrivers()
method, then the (HTTP) response is sent to the client, and only after 0.5~1s I see the getDrivers()
method returning and proceeding to the next method.
Upvotes: 2
Views: 830
Reputation: 338316
Your central problem is, as T.J. Chowder has commented, that you are trying to .filter()
on something that is sometimes a boolean, and sometimes a promise.
You should do the mapping and the filtering in different steps. I tend to prefer the .then()
syntax, so here's my approach:
const getAllTags = (pureTags = false, unused = true, enabled = true, notRevoked = false) => {
return db.dbRfid.findAll()
.then(allTags => allTags.map(tag => Promise.resolve(
enabled === tag.enabled && notRevoked === !tag.revoked && Promise.all([
tag.getDrivers(), tag.getTrucks(), tag.getUsers()
]).then(results => results.some(r => r.length))
).then(ok => ok ? tag : null)))
.then(pendingTags => Promise.all(pendingTags))
.then(resolvedTags => resolvedTags.filter(tag => tag))
.then(filteredTags => filteredTags.map(tag => pureTags ? tag : {
id: tag.id,
rfid: tag.rfid,
expiryDate: tag.expiryDate,
revoked: tag.revoked,
enabled: tag.enabled
}));
};
The logic of this code:
Promise<Tag[]>
, i.e. a promise for an array of tagsnull
, depending on a condition we figure out using Promise.resolve()
, to account for the potentially(*) async nature of that checkPromise<Promise<Tag|null>[]>
Promise.all()
<Promise<(Tag|null)[]>
null
values (i.e. tags we don't want)<Promise<Tag[]>
pureTags
<Promise<(Tag|object)[]>
(*) The Promise.all()
is only invoked if the preconditions check out, due to short circuiting. If not, it's simply a Promise.resolve(false)
, which resolves to false
immediately. In the other case it's going to be Promise.resolve(<Promise<Boolean>)
, which works exactly the same way as Promise.resolve(Boolean)
. This way we can unify the synchronous and the asynchronous test.
In the subsequent .then()
we can decide whether to return the tag
- or null
, to indicate that this tag failed the filter conditions. The null
values are then picked out by resolvedTags.filter(tag => tag)
.
Note that your code serializes the three async checks (getDrivers
, getTrucks
, getUsers
) because each is awaited before the next is started. Using Promise.all()
lets them run in parallel, which is one of the reasons why I dislike using async
/await
for everything.
I leave rewriting this into async
/await
style as an exercise.
Upvotes: 1