Map array items through several functions - javascript

Is there a more elegant way then this to execute several functions in succession for each item in the array:
type Transform<T> = (o: T) => T;
type Item = { /* properties */ };
transform(input, transformers: Transform<Item>[]) {
const items: Item[] = getItems(input);
return items.map(item => {
let transformed = item;
tramsformers.forEach(t => transformed = t(transformed));
return transformed;
})
}

This is a great use case for reduce:
transform(input, transformers: Transform<Item>[]) {
const items: Item[] = getItems(input);
return items.map(item => transformers.reduce((val, transformer) => transformer(val), item));
}
Or perhaps more readably:
transform(input, transformers: Transform<Item>[]) {
const items: Item[] = getItems(input);
return items.map(
item => transformers.reduce(
(val, transformer) => transformer(val),
item
)
);
}
Live Example:
function getItems(input) {
return [
"abcdefg",
"1234567"
];
}
function transform(input, transformers) {
const items = getItems(input);
return items.map(item => transformers.reduce((val, transformer) => transformer(val), item));
}
const result = transform("x", [
v => v.toUpperCase(),
v => v.substring(1, v.length - 1)
]);
console.log(result);
As Nitzan Tomer points out, we could do away with the items constant:
transform(input, transformers: Transform<Item>[]) {
return getItems(input).map(
item => transformers.reduce(
(val, transformer) => transformer(val),
item
)
);
}
I frequently keep those sorts of things for debugging, but some good debuggers now make it easy to see the return value of functions before they return (Chrome's does), so if you removed it, you could step into getItems to see the items before the map.

Here's a bit more reusable version, based on #T.J.Crowder's answer:
export type Transform<T> = (o: T) => T;
export function pipe<T>(sequence: Transform<T>[] = []) {
return (item: T) => sequence.reduce((value, next) => next(value), item);
}
transform(input, transformers?) {
return getItems(input).map( pipe(transformers) );
}
Note that type is inferred from getItems(input) and return type is transform(): Item[].

Related

Javascript - Using compose with reduce

I am learning functional programming with javascript. I have learned that 2 parameters are needed for reduce. Accumalator and the actual value and if we don't supply the initial value, the first argument is used. but I can't understand how the purchaseItem functions is working in the code below. can anyone please explain.
const user = {
name: 'Lachi',
active: true,
cart: [],
purchases: []
}
let history = []
const compose = (f, g) => (...args) => f(g(...args))
console.log(purchaseItem(
emptyCart,
buyItem,
applyTaxToItems,
addItemToCart
)(user, {name: 'laptop', price: 200}))
function purchaseItem(...fns) {
console.log(fns)
return fns.reduce(compose)
}
function addItemToCart (user, item) {
history.push(user)
const updatedCart = user.cart.concat(item)
return Object.assign({}, user, { cart: updatedCart })
}
function applyTaxToItems(user) {
history.push(user)
const {cart} = user
const taxRate = 1.3
const updatedCart = cart.map(item => {
return {
name: item.name,
price: item.price * taxRate
}
})
return Object.assign({}, user, { cart: updatedCart })
}
function buyItem(user) {
history.push(user)
return Object.assign({}, user, { purchases: user.cart })
}
function emptyCart(user) {
history.push(user)
return Object.assign({}, user, {cart: []})
}
Maybe it helps if you take a minimal working example and visualize the output structure:
const comp = (f, g) => x => f(g(x));
const inc = x => `inc(${x})`;
const sqr = x => `sqr(${x})`;
const id = x => `id(${x})`;
const main = [sqr, inc, inc, inc].reduce(comp, id);
console.log(main(0)); // id(sqr(inc(inc(inc(0)))))
Please note that we need id to allow redicung an empty array.
It's a way of creating a pipeline of functions whereby the output from one function is used as the parameter of the next, so we end up with a composed function that is effectively
(...args) =>
emptyCart(
buyItem(
applyTaxToItems(
addItemToCart(...args)
)
)
)
Writing the reduce out in longhand might help in understanding:
fns.reduce((acc, currentFn) => compose(acc, currentFn))

Remove duplicates within the output of a reduce function

I am outputting the matched value of projects when the name of my logo matches the items object within projects with the help of a reduce function. However, whenever I click on multiple logos that both match project.items I am rendering duplicates.
Here is my code:
logos.reduce((acc, logo) => {
if (logo.active) {
Object.values(projects).forEach((proj) => {
if (Object.values(proj.items).includes(logo.name)) {
console.log(acc)
acc.push((<Project title={proj.title} routeName={proj.routeName} items={proj.items} description={proj.description}/>));
}
});
}
return acc
}, [])
My first idea was to create another array, run a for loop and iterate through the values like: filteredValues[i].props.title and push the contents of that loop to an array. I ran run a reduce on that array like this but I was not able to eliminate the duplicate:
const filteredArr = arr.reduce((acc, current) => {
const x = acc.find(item => item.title === current.title);
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
}, []);
Anyway, here's the output of acc which I am using to render my Project component
May be below code is what you need.
const filteredArr = this.getUnique(arr, 'title');
getUnique(arr, comp) {
const unique = arr.map(e => e[comp]).map((e, i, final) => final.indexOf(e) === i && i).filter((e) => arr[e]).map(e => arr[e]);
return unique;
}
Steps involve is to:
Store the comparison values in array "arr.map(e => e[comp])"
Store the indexes of the unique objects ".....).map((e, i, final) => final.indexOf(e) === i && i)"
Eliminate the false indexes & return unique objects ".....).filter((e) => arr[e]).map(e => arr[e])"
You can write your original loop like this
logos
.reduce((acc, logo) => {
if (logo.active) {
Object.values(projects).forEach((proj) => {
if (
Object.values(proj.items).includes(logo.name) &&
!acc.find((item) => item.value === logo.name)
) {
console.log(acc);
acc.push({
value: logo.name,
component: (
<Project
title={proj.title}
routeName={proj.routeName}
items={proj.items}
description={proj.description}
/>
),
});
}
});
}
return acc;
}, [])
.map((values) => values.component);
You can use the Map object to filter out duplicates.
let arrFiltered = [];
// form map of unique arr items
const arrMap = new Map();
arr.forEach(item => arrMap.set(item.title, item));
// form array of all items in map
arrMap.forEach(item => arrFiltered.push(item));
I had to run a reduce function outside of the original forEach loop and check those values with a some function.
logos.reduce((acc, logo) => {
if (logo.active) {
Object.values(projects).forEach((proj) => {
if (Object.values(proj.items).includes(logo.name)) {
console.log(acc)
acc.push((<Project title={proj.title} routeName={proj.routeName} items={proj.items} description={proj.description}/>));
}
});
acc = acc.reduce(function (p, c) {
if (!p.some(function (el) { return el.props.title === c.props.title; })) p.push(c);
return p;
}, []);
}
return acc
}, [])

reduce inside function returns undefined (?)

in this sample using reduce on array of objects works fine but when i insert it all into a function it starts to return undefined... it probably has to do with me not understanding how returning stuff from functions works
const egArrOfObj = [
{
name:'tomato',
},
{
name:'potato',
},
];
console.log(
egArrOfObj
.reduce((acc, curr) => {
return [...acc, curr.name]
} ,[] )
);
const namesFunction = (arrOfObj) => {
arrOfObj
.reduce((acc, curr) => {
return [...acc, curr.name]
} ,[] )
};
const names = namesFunction(egArrOfObj);
console.log(names)
If you add the return statement into your function then it will work as well.
Like the following:
const egArrOfObj = [{
name:'tomato',
},
{
name:'potato',
}];
const namesFunction = (arrOfObj) => {
return arrOfObj.reduce((acc, curr) => {
return [...acc, curr.name]
}, [])
};
console.log(namesFunction(egArrOfObj));
I hope that helps!

lodash mapValues async

Inside _.mapValues I want get some modified values with some latency (for example from DB), but I was faced with problem: while I modify values in sync mode everything is good, when i try use promises or callback its work incorrectly (in first case I get Promise object, in second: undefined value).
Here some simplified example, how i can rewrite code inside mapValues to solve this problem?
'use strict';
const _ = require('lodash');
const Promise = require('bluebird');
let obj = {
one: 1,
two: 2,
};
let increment = (value) => {
return value + 1;
};
let incrementProm = (value) => {
return Promise.resolve(value + 1);
};
let incrementCb = (value, cb) => {
let res = value + 1;
let err = null;
setTimeout(cb.bind(undefined, err, res), 100);
};
let t1 = _.mapValues(obj, (value) => {
return increment(value);
});
let t2 = _.mapValues(obj, (value) => {
return incrementProm(value);
});
let t3 = _.mapValues(obj, (value) => {
let temp;
incrementCb(value, (err, res) => {
temp = res;
});
return temp;
});
console.log('Sync res:');
console.log(t1);
console.log('Promise res:');
console.log(t2);
console.log('Callback res:');
console.log(t3);
You can use bluebird's props() function to resolve all properties with promises.
Promise.props(_.mapValues(obj, incrementProm))
.then(result => console.log(result));
var obj = {
one: 1,
two: 2
};
var incrementProm = value => Promise.resolve(value + 1);
Promise.props(_.mapValues(obj, incrementProm))
.then(result => console.log(result));
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.4.1/bluebird.js"></script>
You're mapping onto promises, so something I do might be:
var _ = require('lodash');
let incrementProm = (value) => {
return Promise.resolve(value + 1);
};
let obj = {
foo: 1,
bar: 2
}
let keys = _.keys(obj);
let promises = _.map(keys, k => {
return incrementProm(obj[k])
.then(newValue => { return { key: k, value: newValue } });
})
Promise.all(promises).then(values => {
values.forEach(v => obj[v.key] = v.value)
})
.then(() => {
// now your object is updated: foo = 2 and bar = 3
console.log(obj);
});
You can implement an async version of mapValues. Something like...
async function mapValuesAsync(object, asyncFn) {
return Object.fromEntries(
await Promise.all(
Object.entries(object).map(async ([key, value]) => [
key,
await asyncFn(value, key, object)
])
)
);
}
Then call it like you would call _.mapValues:
let t2 = await mapValuesAsync(obj, (value) => {
return incrementProm(value);
});
or, simply
let t2 = await mapValuesAsync(obj, incrementProm);
Here's a TypeScript solution, built on top of lodash functions:
import { fromPairs, toPairs, ObjectIterator } from 'lodash';
export async function mapValuesAsync<T extends object, TResult>(
obj: T | null | undefined,
callback: ObjectIterator<T, Promise<TResult>>,
): Promise<{ [P in keyof T]: TResult }> {
return fromPairs(
await Promise.all(
toPairs(obj).map(async ([key, value]) => [
key,
await callback(value, key, obj),
]),
),
) as { [P in keyof T]: TResult };
}
If you don't need types, here's the JavaScript version:
import { fromPairs, toPairs } from 'lodash';
export async function mapValuesAsync(obj, callback) {
return fromPairs(
await Promise.all(
toPairs(obj).map(async ([key, value]) => [
key,
await callback(value, key, obj),
]),
),
);
}
And here's a test, just in case you need it:
import { mapValuesAsync } from './map-values-async';
describe('mapValuesAsync', () => {
it('should map values with an async callback', async () => {
const result = await mapValuesAsync(
{
a: 1,
b: 2,
},
async (value) => {
return await Promise.resolve(value * 2);
},
);
const expected = {
a: 2,
b: 4,
};
expect(result).toEqual(expected);
});
});
This is inspired by Rui Castro's response, posted above, which elegantly makes use of Object.entries and Object.fromEntries. However, that requires es2019 or later to work. Lodash has equivalent functions, toPairs and fromPairs, which work with any flavour of JavaScript.

Filtering an array with a function that returns a promise

Given
let arr = [1,2,3];
function filter(num) {
return new Promise((res, rej) => {
setTimeout(() => {
if( num === 3 ) {
res(num);
} else {
rej();
}
}, 1);
});
}
function filterNums() {
return Promise.all(arr.filter(filter));
}
filterNums().then(results => {
let l = results.length;
// length should be 1, but is 3
});
The length is 3 because Promises are returned, not values. Is there a way to filter the array with a function that returns a Promise?
Note: For this example, fs.stat has been replaced with setTimeout, see https://github.com/silenceisgolden/learn-esnext/blob/array-filter-async-function/tutorials/array-filter-with-async-function.js for the specific code.
Here is a 2017 elegant solution using async/await :
Very straightforward usage:
const results = await filter(myArray, async num => {
await doAsyncStuff()
return num > 2
})
The helper function (copy this into your web page):
async function filter(arr, callback) {
const fail = Symbol()
return (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i=>i!==fail)
}
Demo:
// Async IIFE
(async function() {
const myArray = [1, 2, 3, 4, 5]
// This is exactly what you'd expect to write
const results = await filter(myArray, async num => {
await doAsyncStuff()
return num > 2
})
console.log(results)
})()
// Arbitrary asynchronous function
function doAsyncStuff() {
return Promise.resolve()
}
// The helper function
async function filter(arr, callback) {
const fail = Symbol()
return (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i=>i!==fail)
}
I'll even throw in a CodePen.
As mentioned in the comments, Array.prototype.filter is synchronous and therefore does not support Promises.
Since you can now (theoretically) subclass built-in types with ES6, you should be able to add your own asynchronous method which wraps the existing filter function:
Note: I've commented out the subclassing, because it's not supported by Babel just yet for Arrays
class AsyncArray /*extends Array*/ {
constructor(arr) {
this.data = arr; // In place of Array subclassing
}
filterAsync(predicate) {
// Take a copy of the array, it might mutate by the time we've finished
const data = Array.from(this.data);
// Transform all the elements into an array of promises using the predicate
// as the promise
return Promise.all(data.map((element, index) => predicate(element, index, data)))
// Use the result of the promises to call the underlying sync filter function
.then(result => {
return data.filter((element, index) => {
return result[index];
});
});
}
}
// Create an instance of your subclass instead
let arr = new AsyncArray([1,2,3,4,5]);
// Pass in your own predicate
arr.filterAsync(async (element) => {
return new Promise(res => {
setTimeout(() => {
res(element > 3);
}, 1);
});
}).then(result => {
console.log(result)
});
Babel REPL Demo
For typescript folk (or es6 just remove type syntax)
function mapAsync<T, U>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<U>): Promise<U[]> {
return Promise.all(array.map(callbackfn));
}
async function filterAsync<T>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>): Promise<T[]> {
const filterMap = await mapAsync(array, callbackfn);
return array.filter((value, index) => filterMap[index]);
}
es6
function mapAsync(array, callbackfn) {
return Promise.all(array.map(callbackfn));
}
async function filterAsync(array, callbackfn) {
const filterMap = await mapAsync(array, callbackfn);
return array.filter((value, index) => filterMap[index]);
}
es5
function mapAsync(array, callbackfn) {
return Promise.all(array.map(callbackfn));
}
function filterAsync(array, callbackfn) {
return mapAsync(array, callbackfn).then(filterMap => {
return array.filter((value, index) => filterMap[index]);
});
}
edit: demo
function mapAsync(array, callbackfn) {
return Promise.all(array.map(callbackfn));
}
function filterAsync(array, callbackfn) {
return mapAsync(array, callbackfn).then(filterMap => {
return array.filter((value, index) => filterMap[index]);
});
}
var arr = [1, 2, 3, 4];
function isThreeAsync(number) {
return new Promise((res, rej) => {
setTimeout(() => {
res(number === 3);
}, 1);
});
}
mapAsync(arr, isThreeAsync).then(result => {
console.log(result); // [ false, false, true, false ]
});
filterAsync(arr, isThreeAsync).then(result => {
console.log(result); // [ 3 ]
});
Here's a way:
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var filter = num => wait(1).then(() => num == 3);
var filterAsync = (array, filter) =>
Promise.all(array.map(entry => filter(entry)))
.then(bits => array.filter(entry => bits.shift()));
filterAsync([1,2,3], filter)
.then(results => console.log(results.length))
.catch(e => console.error(e));
The filterAsync function takes an array and a function that must either return true or false or return a promise that resolves to true or false, what you asked for (almost, I didn't overload promise rejection because I think that's a bad idea). Let me know if you have any questions about it.
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var filter = num => wait(1).then(() => num == 3);
var filterAsync = (array, filter) =>
Promise.all(array.map(entry => filter(entry)))
.then(bits => array.filter(entry => bits.shift()));
filterAsync([1,2,3], filter)
.then(results => console.log(results.length))
.catch(e => console.error(e));
var console = { log: msg => div.innerHTML += msg + "<br>",
error: e => console.log(e +", "+ (e.lineNumber-25)) };
<div id="div"></div>
Promise Reducer to the rescue!
[1, 2, 3, 4].reduce((op, n) => {
return op.then(filteredNs => {
return new Promise(resolve => {
setTimeout(() => {
if (n >= 3) {
console.log("Keeping", n);
resolve(filteredNs.concat(n))
} else {
console.log("Dropping", n);
resolve(filteredNs);
}
}, 1000);
});
});
}, Promise.resolve([]))
.then(filteredNs => console.log(filteredNs));
Reducers are awesome. "Reduce my problem to my goal" seems to be a pretty good strategy for anything more complex than what the simple tools will solve for you, i.e. filtering an array of things that aren't all available immediately.
asyncFilter method:
Array.prototype.asyncFilter = async function(f){
var array = this;
var booleans = await Promise.all(array.map(f));
return array.filter((x,i)=>booleans[i])
}
Late to the game but since no one else mentioned it, Bluebird supports Promise.map which is my go-to for filters requiring aysnc processing for the condition,
function filterAsync(arr) {
return Promise.map(arr, num => {
if (num === 3) return num;
})
.filter(num => num !== undefined)
}
Two lines, completely typesafe
export const asyncFilter = async <T>(list: T[], predicate: (t: T) => Promise<boolean>) => {
const resolvedPredicates = await Promise.all(list.map(predicate));
return list.filter((item, idx) => resolvedPredicates[idx]);
};
In case someone is interested in modern typescript solution (with fail symbol used for filtering):
const failSymbol = Symbol();
export async function filterAsync<T>(
itemsToFilter: T[],
filterFunction: (item: T) => Promise<boolean>,
): Promise<T[]> {
const itemsOrFailFlags = await Promise.all(
itemsToFilter.map(async (item) => {
const hasPassed = await filterFunction(item);
return hasPassed ? item : failSymbol;
}),
);
return itemsOrFailFlags.filter(
(itemOrFailFlag) => itemOrFailFlag !== failSymbol,
) as T[];
}
There is a one liner to to do that.
const filterPromise = (values, fn) =>
Promise.all(values.map(fn)).then(booleans => values.filter((_, i) => booleans[i]));
Pass the array into values and the function into fn.
More description on how this one liner works is available here.
For production purposes you probably want to use a lib like lodasync:
import { filterAsync } from 'lodasync'
const result = await filterAsync(async(element) => {
await doSomething()
return element > 3
}, array)
Under the hood, it maps your array by invoking the callback on each element and filters the array using the result. But you should not reinvent the wheel.
You can do something like this...
theArrayYouWantToFilter = await new Promise(async (resolve) => {
const tempArray = [];
theArrayYouWantToFilter.filter(async (element, index) => {
const someAsyncValue = await someAsyncFunction();
if (someAsyncValue) {
tempArray.push(someAsyncValue);
}
if (index === theArrayYouWantToFilter.length - 1) {
resolve(tempArray);
}
});
});
Wrapped within an async function...
async function filter(theArrayYouWantToFilter) {
theArrayYouWantToFilter = await new Promise(async (resolve) => {
const tempArray = [];
theArrayYouWantToFilter.filter(async (element, index) => {
const someAsyncValue = await someAsyncFunction();
if (someAsyncValue) {
tempArray.push(someAsyncValue);
}
if (index === theArrayYouWantToFilter.length - 1) {
resolve(tempArray);
}
});
});
return theArrayYouWantToFilter;
}
A valid way to do this (but it seems too messy):
let arr = [1,2,3];
function filter(num) {
return new Promise((res, rej) => {
setTimeout(() => {
if( num === 3 ) {
res(num);
} else {
rej();
}
}, 1);
});
}
async function check(num) {
try {
await filter(num);
return true;
} catch(err) {
return false;
}
}
(async function() {
for( let num of arr ) {
let res = await check(num);
if(!res) {
let index = arr.indexOf(num);
arr.splice(index, 1);
}
}
})();
Again, seems way too messy.
A variant of #DanRoss's:
async function filterNums(arr) {
return await arr.reduce(async (res, val) => {
res = await res
if (await filter(val)) {
res.push(val)
}
return res
}, Promise.resolve([]))
}
Note that if (as in current case) you don't have to worry about filter() having
side effects that need to be serialized, you can also do:
async function filterNums(arr) {
return await arr.reduce(async (res, val) => {
if (await filter(val)) {
(await res).push(val)
}
return res
}, Promise.resolve([]))
}
Late to the party, and I know that my answer is similar to other already posted answers, but the function I'm going to share is ready for be dropped into any code and be used.
As usual, when you have to do complex operations on arrays, reduce is king:
const filterAsync = (asyncPred) => arr =>
arr.reduce(async (acc,item) => {
const pass = await asyncPred(item);
if(pass) (await acc).push(item);
return acc;
},[]);
It uses modern syntax so make sure your target supports it. To be 100% correct you should use Promise.resolve([]) as the initial value, but JS just doesn't care and this way it is way shorter.
Then you can use it like this:
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const isOdd = x => wait(1).then(()=>x%2);
(filterAsync(isOdd)([1,2,3,4,4])).then(console.log) // => [1,3]
Here's a shorter version of #pie6k's Typescript version:
async function filter<T>(arr: T[], callback: (val: T) => Promise<Boolean>) {
const fail = Symbol()
const result = (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i => i !== fail)
return result as T[] // the "fail" entries are all filtered out so this is OK
}
An efficient way of approaching this is by processing arrays as iterables, so you can apply any number of required operations in a single iteration.
The example below uses library iter-ops for that:
import {pipe, filter, toAsync} from 'iter-ops';
const arr = [1, 2, 3]; // synchronous iterable
const i = pipe(
toAsync(arr), // make our iterable asynchronous
filter(async (value, index) => {
// returns Promise<boolean>
})
);
(async function() {
for await (const a of i) {
console.log(a); // print values
}
})();
All operators within the library support asynchronous predicates when inside an asynchronous pipeline (why we use toAsync), and you can add other operators, in the same way.
Use of Promise.all for this is quite inefficient, because you block the entire array from any further processing that can be done concurrently, which the above approach allows.

Categories