lodash-fp-ex
v1.1.42
Published
lodash/fp extensions
Downloads
403
Maintainers
Readme
lodash-fp-ex
Overview
functions to add in lodash.mixin
Install
$npm i lodash lodash-fp-ex
Usage
import fp from 'lodash/fp';
import lodashFpEx from 'lodash-fp-ex';
fp.mixin(lodashFpEx);
APIs
All functions are curried except promisify.
mapASync
, filterAsync
, reduceAsync
, findAsync
, forEachAsync
, promisify
, andThen
, otherwise
, finally
, isPromise
, isNotEmpty
, isNotNil
, isJson
, notEquals
(isNotEqual
), isVal
(isPrimitive
), isRef
(isReference
), not
, notIncludes
, toBool
, deepFreeze
, key
(keyByVal
), transformObjectKey
, toCamelcase
(toCamelKey
), toSnakecase
(toSnakeKey
), pascalCase
, isDatetimeString
, ap
, instanceOf
, removeByIndex
, removeLast
, append
, prepend
, mapWithKey
(mapWithIndex
), forEachWithKey
(forEachWithIndex
), reduceWithKey
(reduceWithIndex
), isFalsy
, isTruthy
, delayAsync
(sleep
)
mapAsync
mapAsync works with Promise.all
type MapAsync = F.Curry<
<T, K extends keyof T, R>(
asyncMapper: (arg: T[K], key: K) => Promise<R>,
collection: T,
) => Promise<R[]>
>;
(async () => {
const arr = [1, 2, 3, 4, 5];
const obj = { a: 1, b: 2, c: 3 };
const asyncMapper = (a) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(2 * a);
}, 5);
});
// it takes about 5ms + alpha.
const results = await fp.mapAsync(asyncMapper, arr);
// => [2, 4, 6, 8, 10]
const results1 = await fp.mapAsync(asyncMapper, obj);
// => [2, 4, 6]
})();
filterAsync
filterAsync works with Promise.all
type FilterAsync = F.Curry<
<T, K extends keyof T, R>(
asyncFilter: (arg: T[K], key: K) => Promise<boolean>,
collection: T,
) => Promise<R[]>
>;
(async () => {
const arr = [1, 2, 3, 4, 5];
const asyncFilter = (a) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(!fp.equals(0, a % 2));
}, 5);
});
// => it takes about 5ms + alpha.
const results = await fp.filterAsync(asyncFilter, arr);
// => [1,3,5]
})();
reduceAsync
reduceAsync works different way from mapAsync and filterAsync, it works with Promise.resolve. So if you more important function order than performance, reduceAsync is suitable.
type ReduceAsync = F.Curry<
<T, K extends keyof T>(
asyncFn: (acc: any, arg: T[K], key: K) => Promise<any>,
initAcc: Promise<any> | any,
collection: T,
) => Promise<any>
>;
(async () => {
const asyncMapper = (a) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(2 * a);
}, 5);
});
// it takes about (5 * array length)ms + alpha.
const results = await fp.reduceAsync(
async (accP, v) => {
const acc = await accP; // you should await acc first.
const nextVal = await asyncMapper(v);
acc.push(nextVal);
return acc;
},
[],
arr,
);
// => [2, 4, 6, 8, 10]
})();
findAsync
type FindAsync = F.Curry<
<T, K extends keyof T, R>(
asyncFilter: (arg: T[K], key: K) => Promise<boolean>,
collection: T,
) => Promise<R>
>;
(async () => {
const arr = [
{ name: 'hi', age: 21 },
{ name: 'hello', age: 22 },
{ name: 'alo', age: 23 },
];
const asyncFilter = (a) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(fp.pipe(fp.get('name'), fp.equals('hello'))(a));
}, 5);
});
const result = await fp.findAsync(asyncFilter, arr);
console.log(result);
// => { name: 'hello', age: 22 }
})();
forEachAsync
type ForEachAsync = F.Curry<
<T, K extends keyof T, R>(
callbackAsync: (value: T[K], key: K) => Promise<R>,
collection: T,
) => Promise<R[]>
>;
(async () => {
const asyncMapper = (v, i) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(i > 0 ? v * i : v);
}, 5);
});
const results = await fp.forEachAsync(
async (v, i) => {
const nextVal = await asyncMapper(v, i);
return nextVal;
},
[1, 2, 3, 4, 5],
);
// => [1, 2, 6, 12, 20]
})();
(async () => {
const asyncMapper1 = (v, k) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(`${v} ${k}`);
});
}, 5);
const results1 = await fp.forEachAsync(
async (v, k) => {
const nextVal = await asyncMapper1(v, k);
return nextVal;
},
{
key: 'val',
hello: 'world',
'led zeppelin': 'stairway to heaven',
},
);
// => ['val key', 'world hello', 'stairway to heaven led zeppelin']
})();
promisify
wrap argument with Promise
Note: Promisify is not curried to accept Function on first argument. Only when first argument is function, other arguments can be applied.
type Promisify = (a: any, ...args: any[]): Promise<any>
(async () => {
const result = await fp.promisify(128);
// => 128
const result1 = await fp.promisify((a, b) => a + b, 64, 64);
// => 128
const result2 = await fp.promisify(Promise.resolve(128));
// => 128
})();
andThen
Make Promise.then work with fp.pipe
type Then = F.Curry<
(fn: (response: any) => any, thenable: Promise<any>) => Promise<any>
>;
(async () => {
const p = (a) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(2 * a);
}, 5);
});
const composer = fp.pipe(p, fp.andThen(fp.identity));
const result1 = await composer(64);
// => 128
const result2 = await fp.andThen(fp.identity, p(64));
// => 128
const result3 = await fp.pipe(
p,
fp.andThen((x) => x / 2),
)(128);
// => 128
})();
otherwise
Make Promise.catch work with fp.pipe.
type Totherwise = F.Curry<
(
failureHandler: (error: Error | any) => never | any,
thenable: Promise<Error | any>,
) => Promise<never | any>
>;
(async () => {
const p = (a) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (fp.equals(a * a, a)) {
resolve(a);
} else {
reject(new Error('wrong'));
}
});
});
const composer = fp.pipe(p, fp.andThen(fp.identity), fp.catch(fp.identity));
const result1 = await composer(1);
// => 1
const result2 = await composer(2);
// => error 'wrong'
})();
finally
Make Promise.finally work with fp.pipe.
type Finally = F.Curry<
(callback: (...args: any[]) => any, thenable: Promise<any>) => Promise<any>
>;
(async () => {
let isLoading = true;
const p = (a) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (fp.equals(a * a, a)) {
resolve(a);
} else {
reject(new Error('wrong'));
}
});
});
const composer = fp.pipe(
p,
fp.andThen(fp.identity),
fp.catch(fp.identity),
fp.finally(() => (isLoading = false)),
);
await composer(1);
// => false
})();
isPromise
Check argument is promise.
type IsPromise = <T>(x: T): boolean
(() => {
const p = Promise.resolve(1);
const fn = () => 1;
const str = '1';
const num = 1;
fp.isPromise(p);
// => true
fp.isPromise(fn);
// => false
fp.isPromise(str);
// => false
fp.isPromise(num);
// => false
fp.isPromise(null);
// => false
fp.isPromise(undefined);
// => false
})();
isNotEmpty
opposite of lodash.isEmpty
type IsNotEmpty = (a: any) => boolean;
(() => {
fp.isNotEmpty([]);
// => false
fp.isNotEmpty({});
// => false
fp.isNotEmpty(1);
// => false
fp.isNotEmpty(''));
// => false
fp.isNotEmpty('str');
// => true
fp.isNotEmpty(null);
// => false
fp.isNotEmpty(undefined);
// => false
})();
isNotNil
opposite of lodash.isNil
type IsNotNil = (arg: any) => boolean;
(() => {
fp.isNotNil(null);
// => false
fp.isNotNil(undefined);
// => false
fp.isNotNil(1);
// => true
fp.isNotNil({});
// => true
fp.isNotNil(() => {});
// => true
})();
isJson
Check argument is json string.
type IsJson = (arg: any) => boolean;
(() => {
fp.isJson('{ "test": "value" }');
// => true
fp.isJson('test');
// => false
fp.isJson({ test: 'value' });
// => false
})();
notEquals
alias: isNotEqual
opposite of lodash.isEqual
type NotEquals = F.Curry<(a: any, b: any) => boolean>;
(() => {
fp.notEquals({ a: 1 }, { a: 1 });
// => false
fp.notEquals([1, 2, 3], [1, 2, 3]);
// => false
fp.notEquals([1, 2, 3], [2, 3, 4]);
// => true
fp.notEquals('string', 'number');
// => true
fp.notEquals(1, 2);
// => true
})();
isVal
alias: isPrimitive
Check agument is primitive type.
type IsVal = (arg: any) => boolean;
(() => {
fp.isVal(null);
// => true
fp.isVal(undefined);
// => true
fp.isVal(false);
// => true
fp.isVal(1);
// => true
fp.isVal('string');
// => true
fp.isVal([]);
// => false
fp.isVal({});
// => false
fp.isVal(() => {});
// => false
})();
isRef
alias: isReference
Check agument is reference type.
type IsRef = (arg: any) => boolean;
(() => {
fp.isRef(null);
// => false
fp.isRef(undefined);
// => false
fp.isRef(false);
// => false
fp.isRef(1);
// => false
fp.isRef('string');
// => false
fp.isRef([]);
// => true
fp.isRef({});
// => true
fp.isRef(() => {});
// => true
})();
not
Apply ! operator to argument.
type Not = <T>(a: T) => boolean;
(() => {
fp.not(false);
// => true
fp.not(0);
// => true
fp.not('string');
// => false
fp.not(true);
// => false
fp.not(1);
// => false
fp.not({});
// => false
})();
notIncludes
Opposite of lodash.includes
type NotIncludes = F.Curry<
(arg: any, targetArray: any[] | Record<string, any> | string) => boolean
>;
(() => {
fp.notIncludes(1, [1, 2, 3]);
// => false
fp.notIncludes('s', 'string');
// => false
fp.notIncludes(1, { a: 1, b: 2 });
// => false
})();
toBool
'true', 'false' string and other argument convert to Boolean type.
type ToBool = (arg: any) => boolean;
(() => {
fp.toBool(1);
// => true
fp.toBool(0);
// => false
fp.toBool(null);
// => false
fp.toBool(undefined);
// => false
})();
deepFreeze
Reference type target freeze deeply.
type DeepFreeze = (obj: Record<string, any>) => Record<string, any>;
(() => {
const shallowFrozen = Object.freeze({
a: {
b: [],
},
});
const deepFrozen = fp.deepFreeze({
a: {
b: [],
c: () => {},
},
});
Object.isFrozen(shallowFrozen);
// => true
Object.isFrozen(shallowFrozen.a);
// => false
Object.isFrozen(shallowFrozen.a.b);
// => false
Object.isFrozen(deepFrozen);
// => true
Object.isFrozen(deepFrozen.a);
// => true
Object.isFrozen(deepFrozen.a.b);
// => true
Object.isFrozen(deepFrozen.a.c);
// => true
})();
key
alias: keyByVal
Get key string of object by value.
type Key = F.Curry<(obj: Record<string, any>, value: any) => string>;
(() => {
const obj = { a: 1 };
const obj1 = { a: 1, b: 1, c: 1 };
const obj2 = { a: { b: { k: 1 } } };
fp.key(obj, 1);
// => a
fp.key(obj1, 1);
// => c
fp.key(obj2, { b: { k: 1 } });
// => a
fp.key(obj2, { b: { k: 2 } });
// => undefined
})();
transformObjectKey
Argument object key transform with case transform function.
type TransformObjectKey = F.Curry<
(
transformFn: (orignStr: string) => string,
obj: Record<string, any>,
) => Record<string, any>
>;
(() => {
const obj = { obj_key: 1 };
const obj1 = { 'obj-key': 1, obj_key: 2 };
const nestedObj = {
objKey: {
nestedKey: {
anotherKey: [3],
},
},
};
const kebabKeyObj = fp.transformObjectKey(fp.kebabCase, obj);
// => { obj-key: 1 }
const kebabKeyObj1 = fp.transformObjectKey(fp.kebabCase, nestedObj);
// => { 'obj-key': { 'nested-key': { 'another-key': [3] } } }
fp.transformObjectKey(fp.kebabCase, obj1);
// => obj-key already exist. duplicated property name is not supported.
})();
toCamelcase
alias: toCamelKey
Same with transformObjectKey(lodash.camelCase)
type ToCamelcase = (obj: Record<string, any>) => Record<string, any>;
(() => {
const obj = { obj_key: 1 };
const obj1 = { 'obj-key': 1, obj_key: 2 };
const camelKeyObj = fp.toCamelcase(obj);
// => { objKey: 1 }
fp.toCamelcase(obj1);
// => objKey already exist. duplicated property name is not supported.
})();
toSnakecase
alias: toSnakeKey
Same with transformObjectKey(lodash.snakeCase)
type ToSnakecase = (obj: Record<string, any>) => Record<string, any>;
(() => {
const obj = { objKey: 1 };
const obj1 = { objKey: 1, 'obj key': 2 };
const snakeKeyObj = fp.toSnakecase(obj);
// => { obj_key: 1}
fp.toSnakecase(obj1);
// => obj_key already exist. duplicated property name is not supported.
})();
pascalCase
Argument string transform to pascal case.
type PascalCase = (string) => string;
(() => {
const pascals = fp.map(fp.pascalCase, [
'__Foo_Bar__',
'FOO BAR',
'fooBar',
'foo_bar',
'foo-bar',
]);
// => [FooBar, FooBar, FooBar, FooBar, FooBar]
})();
isDatetimeString
Check argument string can parse with Date.parse function.
type IsDatetimeString = (dateStr: string) => boolean;
(() => {
const datetimeStrings = [
'Aug 9, 1995',
'Wed, 09 Aug 1995 00:00:00 GMT',
'Wed, 09 Aug 1995 00:00:00',
'2021/03/14',
'2021-03-14',
'2021/03/14 14:21:00',
'2021-03-14 14:21:00',
'6 Mar 17 21:22 UT',
'6 Mar 17 21:22:23 UT',
'6 Mar 2017 21:22:23 GMT',
'06 Mar 2017 21:22:23 Z',
'Mon 06 Mar 2017 21:22:23 z',
'Mon, 06 Mar 2017 21:22:23 +0000',
];
const invalidDatetimeStrings = ['21:22:23', '20210314'];
fp.every(fp.pipe(fp.isDatetimeString, fp.equals(true)), datetimeStrings);
// => true
fp.every(
fp.pipe(fp.isDatetimeString, fp.equals(false)),
invalidDatetimeStrings,
);
// => true
})();
ap
Inspired by https://github.com/monet/monet.js/blob/master/docs/MAYBE.md#ap
type Ap = F.Curry<(arg: any, curreid: Function) => any>;
(() => {
const includesWithAp = fp.pipe(fp.includes, fp.ap('string'));
const reduceWithAp = fp.pipe(fp.reduce, fp.ap(['f', 'o', 'o']));
const isIncludeI = includesWithAp('i');
// => true
const foo = reduceWithAp((acc, v) => `${acc}${v}`, '');
// => foo
})();
instanceOf
type InstanceOf = F.Curry<<T>(t: any, arg: T) => boolean>;
(() => {
class Car {
constructor(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
}
class C {}
class D {}
const auto = new Car('Honda', 'Accord', 1998);
fp.instanceOf(Car, auto);
// => true
fp.instanceOf(Object, auto);
// => true
fp.instanceOf(C, new C());
// => true
fp.instanceOf(C, new D());
// => false
fp.instanceOf(String, 'string');
// => false
fp.instanceOf(String, new String('string'));
// => true
fp.instanceOf(Object, {});
// => true
})();
removeByIndex
type RemoveByIndex = F.Curry<
<R>(index: number | string, targetArray: R[]) => R[]
>;
(() => {
const arr = [1, 2, 3];
const secondRemoved = fp.removeByIndex(1, arr);
// argument array should not be mutated.
arr;
// => [1, 2, 3]
secondRemoved;
// => [1, 3]
})();
removeLast
type RemoveLast = (target: string | any[]) => string | any[];
(() => {
const arr = [1, 2, 3];
const lastRemoved = fp.removeLast(arr);
// argument array should not be mutated.
arr;
// => [1, 2, 3]
lastRemoved;
// => [1, 2]
})();
append
alias: concat
type Append = F.Curry<<T>(arr: T[], arg: T | T[]) => T[]>;
(() => {
const arr = [1];
const appended = fp.append(arr, 34);
// argument array should not be mutated.
arr;
// => [1]
appended;
// => [1, 34]
fp.append(arr, [2, 3, 4]);
// => [1, 2, 3, 4]
})();
prepend
type Prepend = F.Curry<<T>(arr: T[], arg: T | T[]) => T[]>;
(() => {
const arr = [1];
const prepended = fp.prepend(arr, 34);
// argument array should not be mutated.
arr;
// => [1]
prepended;
// => [34, 1]
fp.prepend(arr, [2, 3, 4]);
// => [2, 3, 4, 1]
})();
mapWithKey
alias: mapWithIndex
Same with fp.map.convert({ cap: false})
type MapWithKey = F.Curry<
<T, K extends keyof T, R>(
iteratee: (value: T[K], key: K) => R,
collection: T,
) => R[]
>;
(() => {
const arr = [3, 4, 5];
const getIdxs = fp.mapWithKey((v, i) => i);
getIdxs(arr);
// => [0, 1, 2]
getIdxs({ a: 1, b: 2 });
// => ['a', 'b']
})();
forEachWithKey
result will same with input (effect function)
alias: forEachWithIndex
Same with fp.map.forEach({ cap: false})
type TforEachWithKey = F.Curry<
<T, K extends keyof T>(
iteratee: (value: T[K], key: K) => T,
collection: T,
) => T
>;
(() => {
const arr = [3, 4, 5];
const getIdxs = fp.forEachWithKey((v, i) => i);
getIdxs(arr);
// => [3, 4, 5]
getIdxs({ a: 1, b: 2 });
// => { a: 1, b: 2 }
})();
reduceWithKey
alias: reduceWithIndex
Same with fp.reduce.convert({ cap: false })
type ReduceWithKey = F.Curry<
<T, K extends keyof T, R>(
iteratee: (acc: R, value: T[K], key: K) => R,
acc: R,
collection: T,
) => R
>;
(() => {
const arr = [3, 4, 5];
const getIdxs = fp.reduceWithKey((acc, v, i) => fp.concat(acc, i), []);
getIdxs(arr);
// => [0, 1, 2]
getIdxs({ a: 1, b: 2 });
// => ['a', 'b']
})();
isFalsy
type isFalsy = (arg: any) => boolean;
() => {
const falsies = [undefined, null, 0, -0, NaN, false, ''];
const notFalsies = [[], '0', 'false', {}, () => {}];
const composer = fp.pipe(fp.map(fp.isFalsy), fp.every(fp.equals(true)));
composer(falses);
// => true
composer(notFalsies);
// => false
};
isTruthy
type IsTruthy = (arg: any) => boolean;
(() => {
const falsies = [undefined, null, 0, -0, NaN, false, ''];
const notFalsies = [[], '0', 'false', {}, () => {}];
const composer = fp.pipe(fp.map(fp.isTruthy), fp.every(fp.equals(false)));
composer(falses);
// => true
composer(notFalsies);
// => false
})();
delayAsync
alias: sleep
type DelayAsync = (ms: number) => Promise<void>;
(async () => {
await fp.delayAsync(300); // 300ms delay
})();