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.