lodash-fp-ex
TypeScript icon, indicating that this package has built-in type declarations

1.1.37 • Public • Published

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(removeByIdx), removeLast, append, prepend, mapWithKey(mapWithIdx, mapWithIndex), forEachWithKey(forEachWithIdx, forEachWithIndex), reduceWithKey(reduceWithIdx, 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

alias: 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

alias: removeByIdx

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: mapWithIdx, 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: forEachWithIdx, 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: reduceWithIdx, 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
})();

Package Sidebar

Install

npm i lodash-fp-ex

Weekly Downloads

46

Version

1.1.37

License

MIT

Unpacked Size

84.8 kB

Total Files

5

Last publish

Collaborators

  • ledzefflin