Imagine two type of content in an app: "Songs" and "Films".
I have two helper methods that are really similar:
/**
* @protected <-- No need to validate arguments, as the method is "protected".
* ...
*/
function fillRemainingSongsList(currentSongs, currentUserId) {
const remainingSongs = MAX_LIST_SIZE - currentSongs.length;
const shouldFetchTrending = Math.random() < FETCH_TRENDING_PROBABILITY;
const promises = [
api.songs.getRandomSongs(),
shouldFetchTrending ? api.songs.getTrendingSongs() : undefined
];
const [
randomSongs = [],
trendingSongs = [],
] = await Promise.all(promises);
return [...currentSongs, ...randomSongs, ...trendingSongs];
}
and
/**
* @protected
* ...
*/
async function fillRemainingFilmsList(currentFilms, category) {
const remainingFilms = MAX_LIST_SIZE - currentFilms.length;
const shouldFetchTrending = Math.random() < FETCH_TRENDING_PROBABILITY;
const promises = [
api.films.getRandomFilms(category),
shouldFetchTrending ? api.films.getTrendingFilms(category) : undefined
];
const [
randomFilms = [],
trendingFilms = [],
] = await Promise.all(promises);
return [...currentFilms, ...randomFilms, ...trendingFilms];
}
As you can see, there is code repetition in both functions. How can I do to generalize them more? Any design pattern?
The problem I am handling is that both methods calls to different api methods, and have different parameters... but, in the other hand, I am trying to not repeat my self in the logic.
You can pass in the random and trending functions into the common refactored function. To cater to the differences in how the different trending functions behave (one requires no parameters and one requires a category) you can wrap them in functions:
async function fillRemaining(currentItems, getRandomFn, getTrendingFn) {
const remainingItems = MAX_LIST_SIZE - currentItems.length; // this is never used?
const shouldFetchTrending = Math.random() < FETCH_TRENDING_PROBABILITY;
const promises = [
getRandomFn(),
shouldFetchTrending ? getTrendingFn() : undefined
];
const [
randomItems = [],
trendingItems = [],
] = await Promise.all(promises);
return [...currentItems, ...randomItems, ...trendingItems];
}
Now both functions can call the refactored function:
function fillRemainingSongsList(currentSongs, currentUserId) {
function getRandom () {
return api.songs.getRandomSongs();
}
function getTrending () {
return api.songs.getTrendingSongs();
}
return fillRemaining(currentSongs, getRandom, getTrending);
}
The above function is written with named functions for clarity. Alternatively you can use anonymous functions:
async function fillRemainingFilmsList(currentFilms, category) {
return fillRemaining(
currentFilms,
() => api.films.getRandomFilms(category),
() => api.films.getTrendingFilms(category)
);
}
Both styles do exactly the same thing.