lodash-fp-ex v1.1.43
lodash-fp-ex
Overview
functions to add in lodash.mixin
Install
$npm i lodash lodash-fp-exUsage
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
})();11 months ago
11 months ago
11 months ago
11 months ago
9 months ago
11 months ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago