BattlefyBlogHistoryOpen menu
Close menuHistory

When to use return vs await vs return await vs nothing

Ronald ChenMay 2nd 2022

async/await is simpler syntax to interact with Promises, but there are multiple ways to write the same code. This blogpost will show when to use return, await, return await or nothing at all.

When to use return

Directly return a promise when the value is intended to be used by the caller. For example, promise of query result can be directly returned.

Good ✅

async function fetchMatch (gameID) {
  const matchID = await api.findMatchIDByGameID(gameID);
  
  return db.findMatchByID(matchID);
}
// type
declare function fetchMatch (gameID: string): Promise<Match>;

Bad ❌

async function fetchMatch (gameID) {
  const matchID = await api.findMatchIDByGameID(gameID);
  
  // pointless to await before return when not inside a try-catch
  return await db.findMatchByID(matchID);
}
async function fetchMatch (gameID) {
  const matchID = await api.findMatchIDByGameID(gameID);
  
  const match = await db.findMatchByID(matchID);
  return match;  // unnecessary declaration
}

When to use await

await should be used for commands to ensure void is returned. The reason why void is returned for command is to enforce command-query separation. Do not mix updates with reads as that violates single-responsibility principle.

Good ✅

async function updateMatchScore (match) {
  const score = calculateScore(match);
  
  await db.updateMatchScoreByID(match.id, score);
}
// type
declare function updateMatchScore (match: Match): Promise<void>;

Bad ❌

async function updateMatchScore (match) {
  const score = calculateScore(match);
  
  return db.updateMatchScoreByID(match.id, score);
}
// type
declare function updateMatchScore (match: Match): Promise<DatabaseUpdateResult>; // oops leaked DatabaseUpdateResult to the caller

When to use return await

return await should only be used inside a try…catch as it is redundant otherwise. This can be enforced with the ESLint rule no-return-await.

Good ✅

Note the await is required for the catch block to work. Without the await, the error will bubble up to the caller without the console.log message.

async function fetchMatch (gameID) {
  try {
    return await api.findMatchIDByGameID(gameID);
  } catch (error) {
    console.log('failed to find match for game %s: %O',
      gameID,
      error
    );
    
    throw error;
  }
}

Bad ❌

async function fetchMatch (gameID) {
  try {
    return api.findMatchIDByGameID(gameID);
  } catch (error) {
    // this catch won't be used if api.findMatchIDByGameID
    // is rejected. the rejected promised would go directly
    // to the caller as there is no await
    console.log('failed to find match for game %s: %O',
      gameID,
      error
    );
    
    throw error;
  }
}
async function fetchMatch (gameID) {
  const matchID = await api.findMatchIDByGameID(gameID);
  
  return await db.findMatchByID(matchID);  // pointless await
}

When to use nothing

Do not await all promises. Sometime a promise should be a side-effect to prevent blocking the async function or causing the entire async function to be rejected when the promise fails.

Good ✅

async function fetchMatch (gameID) {
  const matchID = await api.findMatchIDByGameID(gameID);
  
  // we don't care if track becomes a rejected promise
  track({
    type: 'query',
    query: 'fetchMatch',
    arguments: {
      gameID
    },
    result: {
      matchID
    }
  });
  
  return db.findMatchByID(matchID);
}

Bad ❌

async function fetchMatch (gameID) {
  const matchID = await api.findMatchIDByGameID(gameID);
  
  // if track rejects, then a match won't be returned
  await track({
    type: 'query',
    query: 'fetchMatch',
    arguments: {
      gameID
    },
    result: {
      matchID
    }
  });
  
  return db.findMatchByID(matchID);
}

Did you know all these scenarios? We’d like to talk to you! Battlefy is hiring.

2022

Powered by
BATTLEFY