How to apply the DRY principle to async/await functions without encountering excessive overhead?

I have two async functions which duplicate a lot of code with each other, and I’m trying to figure out a way to break this duplicate code out into an alternate function which is called by both functions.

My first function looks like this (in reality there is a lot more code related to the message to aggregate):

const getAllMessageStats() = async (userId: string) => {
  const messageIds = await getUserMessageIds(userId);
 
  const messagesRead = 0;
  const reactionsTotal = 0;
  const moreStats = [];

  for (const messageId of messageIds) {
    const message = await getMessage(messageId);

    if (message.read) messagesRead++;
    reactionsTotal += message.reactions;

    const otherStats = [];
    for (const otherDataId of message.otherDataIds) {
      otherData = await getOtherData(otherDataId)
      otherStats.push(doLotsOfOtherCalcs(otherData));
    }
    moreStats.push(otherStats);
  }

  return {
    messagesRead,
    reactionsTotal,
    moreStats
  }
};

My second function looks something like this

const getMessageStats() = async (messageId: string) => {
  const message = await getMessage(messageId);
  const otherStats = [];
  for (const otherDataId of message.otherDataIds) {
    otherData = await getOtherData(otherDataId)
    otherStats.push(doLotsOfOtherCalcs(otherData));
  }
  const miscData = await getMiscData(messageId);

  return {
    read: message.read,
    reactions: message.reactions,
    otherStats,
    miscData
}

I tried the following:

const sharedFunction() = async (messageId: string) => {
  const message = await getMessage(messageId);
  const otherStats = [];
  for (const otherDataId of message.otherDataIds) {
    otherData = await getOtherData(otherDataId)
    otherStats.push(doLotsOfOtherCalcs(otherData));
  }
  return {
    read: message.read,
    reactions: message.reactions,
    otherStats
  }
};

const getMessageStats() = async (messageId: string) => {
  const { read, reactions, otherStats } = await sharedFunction(messageId);
  const miscData = await getMiscData(messageId);
  return {
    read,
    reactions,
    otherStats,
    miscData
  }
};

const getAllMessageStats() = async (userId: string) => {
  const messageIds = await getUserMessageIds(userId);
 
  const messagesRead = 0;
  const reactionsTotal = 0;
  const moreStats = [];

  for (const messageId of messageIds) {
    const { read, reactions, otherStats } = await sharedFunction(messageId);

    if (read) messagesRead++;
    reactionsTotal += reactions;
    moreStats.push(otherStats);
  }

  return {
    messagesRead,
    reactionsTotal,
    moreStats
  }
};

However, my function getAllMessageStats now takes over 3 times as long to execute, especially when I have thousands of messages for a user. After coming across this post about async/await overhead, I now understand why. But my question is: am I now stuck with my original duplicated code? Surely there is a more elegant solution right?