Send multiple REST requests at the same time. (node)
When you have multiple asynchronous requests to make, and you want them to operate in parallel, usually the appropriate tool to use is Promise.all. You can use it here to wait for the groups and the profile data at once, despite the fact that the two requests are pretty distinct from each other:
async function gatherUserInfo(userId) {
  try {
    const [groups, {username, name}] = await Promise.all([
      getGroups(userId),
      getProfile(userId),
    ])
    return {
      username,
      name,
      groups,
      statusText: await getStatusText(username),
    }
  } catch (err) {
    if (err instanceof NotLoggedInError) {
      return null
    }
    throw err
  }
}

This way, if any one of the Promises rejects, it will be being awaited at that time, so it'll be caught properly.


async function gatherUserInfo(userId) {
  try {
    const [groups, {username, name}] = await Promise.all([
      getGroups(userId),
      getProfile(userId),
    ])
    return {
      username,
      name,
      groups,
      statusText: await getStatusText(username),
    }
  } catch (err) {
    if (err instanceof NotLoggedInError) {
      return null
    }
    throw err
  }
}



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 =&gt; new Promise(resolve =&gt; 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
})()



You could also have fixed the unhandled rejection by adding a no-op .catch handler to the Promise that was created before awaiting the other Promise:
groupsRequest.catch(() =&gt; void 0);

But that makes the code less elegant - I think the Promise.all version is better.


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)
    groupsRequest.catch(() =&gt; void 0);

    const {username, name} = await getProfile(userId)
    return {
      username,
      name,
      groups: await groupsRequest,
      statusText: await getStatusText(username),
    }
  } catch (err) {
    if (err instanceof NotLoggedInError) {
      return null
    }
    throw err
  }
}



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 =&gt; new Promise(resolve =&gt; 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
})()