In this particular case, I suggest you implement all of your suggestions. That might sound a little mad, but let me explain.
Let’s consider your last function first:
function getFromDB (fromThisTable, getThis, byThis, callback)
{
var tableName;
switch (fromThisTable.toLowerCase()) {
case 'shop': tableName = 'OnlineShopUser';
...
}
var fieldName;
switch (byThis.toLowerCase()) {
case 'byemail': fieldName = 'EmailAddress';
...
}
... // fetch from database, e.g.:
return axios.get(...).then(callback);
}
Now we can declare several more specialised functions that make use of the above one:
function getUserByUsername (username, callback) => {
return getFromDB ('shop', username, 'byUsername', callback);
}
function getUserByEmail (username, callback) => {
return getFromDB ('shop', username, 'byEmail', callback);
}
The principle is that you start off defining general-purpose functions, and then you define more specialised functions using them. You might declare even more specialised functions in terms of those, and so on.
The idea is that the more general-purpose functions ‘factor out’ a certain amount of code that you would otherwise simply repeat again and again in the more specialised functions. However, calling the specialised functions is better, because there are fewer parameters to give, and so there is less to go wrong.
The more specialised functions also help a little with ‘self-documentation’, in that they are better at saying what they do (or, indeed, what they are intended to do). You should generally augment self-documentation with useful comments, by the way.
The functions at every level should be unit tested.
Having functions declared at many levels tends to aid unit testing, because unit tests on a particular function cannot test faults in the calls to that function (they can only test what happens inside the function). If calls to that function are wrapped inside another function, you can unit test the outer (wrapper) function and so test (most or all of) the calls to the inner function. Or, in other words, the more functions, the more ‘surface area’ you have for unit testing. That tends to be good.
In other circumstances, it can be a question of careful choice as to what level you pitch your functions (and other abstractions) at. The idea is to choose functions that say what they do, as concisely and strongly as possible. 99 times out of 100 the most important thing is making your code easy for other programmers to understand. The odd 1 in 100 case is where you need to do something unintuitive for the sake of speed or compatibility. In such cases, ensure you use comments or other documentation to explain what is going on.
HTH