# lodash-fp-ex

## Overview

functions to add in lodash.mixin

## Install

```bash
$npm i lodash lodash-fp-ex
```

## Usage

```javascript
import fp from 'lodash/fp';
import lodashFpEx from 'lodash-fp-ex';

fp.mixin(lodashFpEx);
```

## APIs

All functions are curried except promisify.

[`mapASync`](#mapasync), [`filterAsync`](#filterasync), [`reduceAsync`](#reduceasync), [`findAsync`](#findasync), [`forEachAsync`](#foreachasync), [`promisify`](#promisify), [`andThen`](#andthen), [`otherwise`](#otherwise), [`finally`](#finally), [`isPromise`](#ispromise), [`isNotEmpty`](#isnotempty), [`isNotNil`](#isnotnil), [`isJson`](#isjson), [`notEquals`](#notequals)(`isNotEqual`), [`isVal`](#isval)(`isPrimitive`), [`isRef`](#isref)(`isReference`), [`not`](#not), [`notIncludes`](#notincludes), [`toBool`](#tobool), [`deepFreeze`](#deepfreeze), [`key`](#key)(`keyByVal`), [`transformObjectKey`](#transformobjectkey), [`toCamelcase`](#tocamelcase)(`toCamelKey`), [`toSnakecase`](#tosnakecase)(`toSnakeKey`), [`pascalCase`](#pascalcase), [`isDatetimeString`](#isdatetimestring), [`ap`](#ap), [`instanceOf`](#instanceof), [`removeByIndex`](#removebyindex), [`removeLast`](#removelast), [`append`](#append), [`prepend`](#prepend), [`mapWithKey`](#mapwithkey)(`mapWithIndex`), [`forEachWithKey`](#foreachwithkey)(`forEachWithIndex`), [`reduceWithKey`](#reducewithkey)(`reduceWithIndex`), [`isFalsy`](#isfalsy), [`isTruthy`](#istruthy), [`delayAsync`](#delayasync)(`sleep`)

### mapAsync

mapAsync works with Promise.all

```ts
type MapAsync = F.Curry<
  <T, K extends keyof T, R>(
    asyncMapper: (arg: T[K], key: K) => Promise<R>,
    collection: T,
  ) => Promise<R[]>
>;
```

```javascript
(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

```ts
type FilterAsync = F.Curry<
  <T, K extends keyof T, R>(
    asyncFilter: (arg: T[K], key: K) => Promise<boolean>,
    collection: T,
  ) => Promise<R[]>
>;
```

```javascript
(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.

```ts
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>
>;
```

```javascript
(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

```ts
type FindAsync = F.Curry<
  <T, K extends keyof T, R>(
    asyncFilter: (arg: T[K], key: K) => Promise<boolean>,
    collection: T,
  ) => Promise<R>
>;
```

```javascript
(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

```ts
type ForEachAsync = F.Curry<
  <T, K extends keyof T, R>(
    callbackAsync: (value: T[K], key: K) => Promise<R>,
    collection: T,
  ) => Promise<R[]>
>;
```

```javascript
(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.**

```ts
type Promisify = (a: any, ...args: any[]): Promise<any>
```

```javascript
(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

```ts
type Then = F.Curry<
  (fn: (response: any) => any, thenable: Promise<any>) => Promise<any>
>;
```

```javascript
(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.

```ts
type Totherwise = F.Curry<
  (
    failureHandler: (error: Error | any) => never | any,
    thenable: Promise<Error | any>,
  ) => Promise<never | any>
>;
```

```javascript
(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.

```ts
type Finally = F.Curry<
  (callback: (...args: any[]) => any, thenable: Promise<any>) => Promise<any>
>;
```

```javascript
(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.

```ts
type IsPromise = <T>(x: T): boolean
```

```javascript
(() => {
  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

```ts
type IsNotEmpty = (a: any) => boolean;
```

```javascript
(() => {
  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

```ts
type IsNotNil = (arg: any) => boolean;
```

```javascript
(() => {
  fp.isNotNil(null);
  // => false
  fp.isNotNil(undefined);
  // => false
  fp.isNotNil(1);
  // => true
  fp.isNotNil({});
  // => true
  fp.isNotNil(() => {});
  // => true
})();
```

### isJson

Check argument is json string.

```ts
type IsJson = (arg: any) => boolean;
```

```javascript
(() => {
  fp.isJson('{ "test": "value" }');
  // => true
  fp.isJson('test');
  // => false
  fp.isJson({ test: 'value' });
  // => false
})();
```

### notEquals

**alias:** isNotEqual

opposite of lodash.isEqual

```ts
type NotEquals = F.Curry<(a: any, b: any) => boolean>;
```

```javascript
(() => {
  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.

```ts
type IsVal = (arg: any) => boolean;
```

```javascript
(() => {
  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.

```ts
type IsRef = (arg: any) => boolean;
```

```javascript
(() => {
  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.

```ts
type Not = <T>(a: T) => boolean;
```

```javascript
(() => {
  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

```ts
type NotIncludes = F.Curry<
  (arg: any, targetArray: any[] | Record<string, any> | string) => boolean
>;
```

```javascript
(() => {
  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.

```ts
type ToBool = (arg: any) => boolean;
```

```javascript
(() => {
  fp.toBool(1);
  // => true

  fp.toBool(0);
  // => false
  fp.toBool(null);
  // => false
  fp.toBool(undefined);
  // => false
})();
```

### deepFreeze

Reference type target freeze deeply.

```ts
type DeepFreeze = (obj: Record<string, any>) => Record<string, any>;
```

```javascript
(() => {
  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.

```ts
type Key = F.Curry<(obj: Record<string, any>, value: any) => string>;
```

```javascript
(() => {
  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.

```ts
type TransformObjectKey = F.Curry<
  (
    transformFn: (orignStr: string) => string,
    obj: Record<string, any>,
  ) => Record<string, any>
>;
```

```javascript
(() => {
  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)

```ts
type ToCamelcase = (obj: Record<string, any>) => Record<string, any>;
```

```javascript
(() => {
  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)

```ts
type ToSnakecase = (obj: Record<string, any>) => Record<string, any>;
```

```javascript
(() => {
  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.

```ts
type PascalCase = (string) => string;
```

```javascript
(() => {
  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.

```ts
type IsDatetimeString = (dateStr: string) => boolean;
```

```javascript
(() => {
  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>

```ts
type Ap = F.Curry<(arg: any, curreid: Function) => any>;
```

```javascript
(() => {
  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

```ts
type InstanceOf = F.Curry<<T>(t: any, arg: T) => boolean>;
```

```javascript
(() => {
  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

```ts
type RemoveByIndex = F.Curry<
  <R>(index: number | string, targetArray: R[]) => R[]
>;
```

```javascript
(() => {
  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

```ts
type RemoveLast = (target: string | any[]) => string | any[];
```

```javascript
(() => {
  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

```ts
type Append = F.Curry<<T>(arr: T[], arg: T | T[]) => T[]>;
```

```javascript
(() => {
  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

```ts
type Prepend = F.Curry<<T>(arr: T[], arg: T | T[]) => T[]>;
```

```javascript
(() => {
  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})

```ts
type MapWithKey = F.Curry<
  <T, K extends keyof T, R>(
    iteratee: (value: T[K], key: K) => R,
    collection: T,
  ) => R[]
>;
```

```javascript
(() => {
  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})

```ts
type TforEachWithKey = F.Curry<
  <T, K extends keyof T>(
    iteratee: (value: T[K], key: K) => T,
    collection: T,
  ) => T
>;
```

```js
(() => {
  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 })

```ts
type ReduceWithKey = F.Curry<
  <T, K extends keyof T, R>(
    iteratee: (acc: R, value: T[K], key: K) => R,
    acc: R,
    collection: T,
  ) => R
>;
```

```javascript
(() => {
  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

```ts
type isFalsy = (arg: any) => boolean;
```

```javascript
() => {
  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

```ts
type IsTruthy = (arg: any) => boolean;
```

```javascript
(() => {
  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

```ts
type DelayAsync = (ms: number) => Promise<void>;
```

```js
(async () => {
  await fp.delayAsync(300); // 300ms delay
})();
```
