1

I wrote this function below which transforms the passed array of products by product type and currency type

function getProductsByCurrency(products, type, exchangeRate = 1) {
    var productsRetrieved = products.map(item => ({id: item.id,
        name: item.name,
        price: (item.price * exchangeRate).toFixed(2),
        type: type}));
    return productsRetrieved;       
}

Is it possible to break down the function to be more specific? or design it in a better way? For example by naming it getProductsByCurrency it doesn't look right because if I am using it with default rate, i can pass books array to retrieve products with 'books' type independent of exchange rate. Perhaps there is a way to use partial functions(FP)?

Edit: Adding more context to what I am trying to achieve.

Lets say I have three categories of products(phones, cosmetics, books) coming from three resources. I need to create three consolidated arrays of all the products by different currencies(productsinUSD, productsinAUD, productsinPounds)

Also using below function to consolidate the arrays

    function concatProducts(arr) {
        return [].concat.apply([], arr);
    }

So I am calling getProductsByCurrency three times to transform them by product type and currency(exchange rate) and passing those values as an array to concat them to achieve productsinUSD. And repeat to get productsinAUD, productsinPounds.

Also type is string value(ex: 'mobiles')

duplode
  • 33,731
  • 7
  • 79
  • 150
nick bing
  • 171
  • 1
  • 12
  • 1
    it is unclear (at least to me) what is you are asking.. – Nikos M. Sep 05 '19 at 09:53
  • 1
    Can you add 2 different examples of how you want to use the function and the respective results? – nick zoum Sep 05 '19 at 09:53
  • Its unclear if you want the function to work for a wider variety of products or for a specific product. (More abstract or less abstract) – nick zoum Sep 05 '19 at 09:57
  • Added more context hope its clear now. – nick bing Sep 05 '19 at 10:14
  • 2
    You should always write functions that are as simple as possible and require as little context as possible. Your `getProductsByCurrency` expects an `Array` of products, but it should only expect a single product. Do the mapping outside the function. –  Sep 05 '19 at 10:26
  • @nickbing so are you looking for 1 abstract function and 3 implementations for the 3 types? – nick zoum Sep 05 '19 at 10:26
  • If you want to make a function more general, make it a higher order function, i.e. a function that is passed a function argument for the part of its implementation you want to be more flexible with. –  Sep 05 '19 at 10:32
  • @nickzoum I don't what that means but I am just looking to make it more simpler/sensible because i am calling getProductsByCurrency 9 times when you have three types of products and three currencies. If you understand what I am trying to achieve may be suggest a better way to do this. thanks – nick bing Sep 05 '19 at 10:33
  • @bob some thing like that(partial function), is it possible for you to show some code? – nick bing Sep 05 '19 at 10:34
  • @bob thanks for mapping outside the function idea, that makes more sense – nick bing Sep 05 '19 at 10:49

3 Answers3

4

First there's nothing really wrong with the function you posted. There's a few things I'd do differently, but I'm not going to pretend this isn't splitting hairs a bit.

const processItem = (type, exchangeRate = 1) => ({
  id,
  price,
  name,
}) => ({
  id,
  name,
  type,
  price: (price * exchangeRate).toFixed(2),
});

We have a function that takes a type and an optional exchangeRate and returns a function that converts a single item to the form you want. This is what bob was talking about in the comments. I've also used object destructuring on the item and property shorthand on the result to make the code cleaner. Now we can map it over various categories of stuff:

const results = [
   ...mobilePhones.map(processItem('phone')),
   ...cosmetics.map(processItem('cosmetics')),
   ...books.map(processItem('book')),
];

If you need the interim results for some other purpose, just stuff them in vars but I've spread them directly into the results array for simplicity.

While this is shorter, clearer, and more flexible than the one you posted for the code quality hat trick, I'd like to reiterate that I've seen way, way worse than the function you posted.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • This is perfect! I am little confused how it works. When you map it, you are only passing the type but not the actual item? how does it know to take the item? – nick bing Sep 05 '19 at 12:18
  • 1
    The type gets passed in which returns a function that *then* gets passed to map, and map feeds it the items one at a time. – Jared Smith Sep 05 '19 at 13:16
  • Yep, got it. Thanks.Don't you think in the above solution we are losing the readability and don't know what actually is happening in ```processItem``` unless we go look for it. If so, is there a way to prevent it? – nick bing Sep 05 '19 at 13:41
  • 1
    Better naming would help: calling something like inventoryTransformerCallbackFactory with 'inventory' possibly replaced by something more domain-specific. I'm just usually too lazy to pick good names for SO answers :D. – Jared Smith Sep 05 '19 at 15:00
1

There is nothing wrong with what you've done, but you could also create 3 more functions to call that in turn call getProductsByCurrency with the respective type.

var example = JSON.parse(`[{"id":1,"name":"1","price":5},{"id":2,"name":"2","price":15},{"id":3,"name":"3","price":20}]`);

function getProductsByCurrency(type, products, exchangeRate = 1) {
  return products.map(item => ({
    id: item.id,
    name: item.name,
    price: (item.price * exchangeRate).toFixed(2),
    type: type
  }));
}

function getPhonesByCurrency() {
  return getProductsByCurrency("phone", ...arguments);
}

function getCosmeticsByCurrency() {
  return getProductsByCurrency("cosmetic", ...arguments);
}

function getBooksByCurrency() {
  return getProductsByCurrency("book", ...arguments);
}

console.log([].concat(getPhonesByCurrency(example), getCosmeticsByCurrency(example, 0.5), getCosmeticsByCurrency(example, 2)));

You might also prefer wrapping those 3 functions in an object (more tidy and helps IDE with autocomplete)

var example = JSON.parse(`[{"id":1,"name":"1","price":5},{"id":2,"name":"2","price":15},{"id":3,"name":"3","price":20}]`);

function getProductsByCurrency(type, products, exchangeRate = 1) {
  return products.map(item => ({
    id: item.id,
    name: item.name,
    price: (item.price * exchangeRate).toFixed(2),
    type: type
  }));
}

const getByCurrency = {
  phones: function() {
    return getProductsByCurrency("phone", ...arguments);
  },
  cosmetics: function() {
    return getProductsByCurrency("cosmetic", ...arguments);
  },
  books: function() {
    return getProductsByCurrency("book", ...arguments);
  }
};

console.log([].concat(getByCurrency.phones(example), getByCurrency.cosmetics(example, 0.5), getByCurrency.books(example, 2)));
nick zoum
  • 7,216
  • 7
  • 36
  • 80
0

that depends on the kind of data you're feeding into these functions. if you'll pass different arrays of objects (that all have a type property) than I reckon you'd have a function that filters arrays by type ( or any other property and condition that is common between different sets of data). You could than chain your filter function with your mapping function. Your mapping function seems that it needs to be specific to the currenncy as you're plucking certain props from the object, than computing some of the values before you return it.

Hope this helps

  • type is a string value like 'mobiles' , 'books'. Should have made it more clear. So i don't think filter works here – nick bing Sep 05 '19 at 10:20