Below is a (very) simplified version of a function I made that creates multiple REST requests to gather needed information.
Things to note:
- One of the REST calls is dependent upon the output of another.
- I want to send off multiple requests at the same time when possible
- If any rest responses inform us that the user is logged out, null should be returned.
async function gatherUserInfo(userId) {
try {
// Don't await yet, so this request can be sent at the same time as future requests.
const groupsRequest = getGroups(userId)
const {username, name} = await getProfile(userId)
return {
username,
name,
statusText: await getStatusText(username),
groups: await groupsRequest,
}
} catch (err) {
if (err instanceof NotLoggedInError) {
return null
}
throw err
}
}
I found the above code to be a very elegant solution to the problem. However, I recently realized that it won't actually work in node, because node will give warning or stop execution (depending on version) if an uncaught error happens in a promise. In the example above, the getGroups() call would send off a request (but not await it). Then, this function execution will pause on getProfile(). In the meantime, getGroups() will come back with the NotLoggedInError, which will result in the process being terminated, because that promise has not been awaited yet, (the try-catch can't catch the error until the await happens, which does happen later in the function execution).
So, I reluctantly trashed my old design and coded this up instead:
async function gatherUserInfo(userId) {
const requests = [];
requests.push(
getGroups(userId)
.then(groups => ({groups}))
)
requests.push(
getProfile(userId)
.then(async ({username, name}) => {
const statusText = await getStatusText(username)
return {username, name, statusText}
})
)
let responses
try {
responses = await Promise.all(requests)
} catch (err) {
if (err instanceof NotLoggedInError) {
return null
}
throw err
}
return Object.assign({}, ...responses)
}
This is much harder to follow or understand, but it's the best I could come up with. How can this code be improved?
Here's some stub methods and test cases that can be paired with the examples above to execute them:
// Mocks //
async function getGroups(userid) {
await wait(50)
if (userid !== 12) throw new NotLoggedInError('User not logged in')
return ['group A', 'group B'];
}
async function getProfile(userid) {
await wait(30)
if (userid !== 12) throw new NotLoggedInError('User not logged in')
return { username: 'grinchMaster135', name: 'The Grinch' };
}
async function getStatusText(username) {
await wait(30)
return 'away'
}
class NotLoggedInError extends Error {}
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
// TEST //
;(async function() {
console.log('Grinch info:', await gatherUserInfo(12)) // Grinch info: { username: 'grinchMaster135', name: 'The Grinch', groups: [ 'group A', 'group B' ], statusText: 'away' }
console.log('Non-logged in user info:', await gatherUserInfo(13)) // Non-logged in user info: null
})()
```