Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/async/modules/flat #354

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/core/async/modules/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ import type {

} from 'core/async/interface';

import flatAsync from 'core/async/modules/flat';

export * from 'core/async/modules/base/const';
export * from 'core/async/modules/base/helpers';

export * from 'core/async/interface';
export * from 'core/async/modules/flat/interface';

export default class Async<CTX extends object = Async<any>> {
/**
Expand All @@ -48,6 +51,16 @@ export default class Async<CTX extends object = Async<any>> {
*/
static linkNames: NamespacesDictionary = namespaces;

/**
* {@link flatAsync}
*/
static flat: typeof flatAsync = flatAsync;

/**
* {@link flatAsync}
*/
flat: typeof flatAsync = flatAsync;

/**
* The lock status.
* If true, then all new tasks won't be registered.
Expand Down
16 changes: 16 additions & 0 deletions src/core/async/modules/flat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Changelog
=========

> **Tags:**
> - :boom: [Breaking Change]
> - :rocket: [New Feature]
> - :bug: [Bug Fix]
> - :memo: [Documentation]
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.?? (2023-??-??)

#### :rocket: New Feature

* Creating the module
45 changes: 45 additions & 0 deletions src/core/async/modules/flat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# core/async/modules/flat

This module provides a function for flatly working with a sequence of promises.
The function uses the `Proxy` object for creating a chain of promises where each next promise gets a value from the previous one.

You can either specify a value that will be then wrapped in a `Promise` or a function which return value also will be wrapped in a `Promise`:

```typescript
import flatAsync from 'core/async/modules/flat';

function getData(): Promise<Array<Promise<number>>> {
return Promise.resolve([Promise.resolve(21)]);
}

// "21"
const str1 = await flatAsync(getData)().at(0)?.toFixed();

// "21"
const str2 = await flatAsync(getData())[0].toFixed();
```

The function is also available as a static method of the `Async` class or as a method of its instance:

```typescript
import Async from 'core/async';

Async.flat(promise);

new Async().flat(promise);
```

The module also provides the `Promisify` type for patching your value:

```typescript
import type { Promisify } from 'core/async/modules/flat';

const val: Promisify<number> = Promise.resolve(21);

val
.toString()
.split('')
.then()
.catch()
.finally();
```
69 changes: 69 additions & 0 deletions src/core/async/modules/flat/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/

/**
* The function implements the logic of chaining promises flatly using the `Proxy` object.
* It creates a chain of promises where each next promise takes a value from the previous one.
*
* @param getPrevPromiseLike - the function that returns the previous `PromiseLike` in chain
*/
export function proxymify<T>(getPrevPromiseLike: (...args: unknown[]) => PromiseLike<T>): unknown {
return new Proxy(getPrevPromiseLike, {
get(_: unknown, nextProp: string): unknown {
const prevPromiseLike = getPrevPromiseLike();
return handleNativePromise(prevPromiseLike, nextProp) ?? proxymifyNextValue(prevPromiseLike, nextProp);
},

apply(target: (...args: unknown[]) => PromiseLike<Function>, _: unknown, args: unknown[]): unknown {
return proxymifyNextValueFromMethodCall(target, args);
}
});
}

/**
* Checks if the passed prop is in the `Promise.prototype` and tries to get value by this prop.
*
* @param prevPromiseLike - previous `PromiseLike`
* @param nextProp - possible key from `Promise.prototype`
*/
function handleNativePromise<T>(prevPromiseLike: PromiseLike<T>, nextProp: string | symbol): unknown {
if (!Object.hasOwnProperty(Promise.prototype, nextProp)) {
return;
}

const value = prevPromiseLike[nextProp];
return Object.isFunction(value) ? value.bind(prevPromiseLike) : value;
}

/**
* Creates next promise in chain that gets a value from the previous one by accessing it using the specified prop.
*
* @param prevPromiseLike - previous `PromiseLike`
* @param nextProp - key to get next value
*/
function proxymifyNextValue<Data>(prevPromiseLike: PromiseLike<Data>, nextProp: string | symbol): unknown {
return proxymify(async () => {
const data = await prevPromiseLike;
const value = data[nextProp];
return Object.isFunction(value) ? value.bind(data) : value;
});
}

/**
* Creates next promise in chain that gets value from the previous one by calling the function.
* This function is called when we try to call a method on the previous proxied object.
*
* @param getMethod - the function that returns `PromiseLike` with currently calling method
* @param args - arguments for the method
*/
function proxymifyNextValueFromMethodCall(getMethod: () => PromiseLike<Function>, args: unknown[]): unknown {
return proxymify(async () => {
const method = await getMethod();
return method(...args);
});
}
52 changes: 52 additions & 0 deletions src/core/async/modules/flat/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/

import type { Promisify } from 'core/async/modules/flat/interface/promisify';
import { proxymify } from 'core/async/modules/flat/helpers';

export * from 'core/async/modules/flat/interface';

export function flatAsync<T extends AnyFunction>(fn: T): Promisify<T>;

export function flatAsync<T>(value: CanPromiseLike<T>): Promisify<T>;

/**
* The function allows you to work flatly with promises using the `Proxy` object.
*
* The value you pass will be patched using the `Promisify` type in such a way that
* each of its members will be wrapped in a promise. However, you can still work with this value
* without worrying about the nested promises.
*
* @param value
* Can be any value or a function that returns any value.
* The final value will be wrapped in the `Promise`.
*
* @example
* ```typescript
* function getData(): Promise<Promise<number>[]> {
* return Promise.resolve([Promise.resolve(21)]);
* }
*
* // "21"
* const str1 = await flatAsync(getData)()[0].toFixed(1);

* // "21"
* const str2 = await flatAsync(getData())[0].toFixed(1);
* ```
*/
export default function flatAsync<T>(
value: CanPromiseLike<T> | AnyFunction
): Promisify<T> {
if (Object.isFunction(value)) {
return Object.cast((...args: unknown[]) => proxymify(
() => Promise.resolve(value(...args))
));
}

return Object.cast(proxymify(() => Promise.resolve(value)));
}
17 changes: 17 additions & 0 deletions src/core/async/modules/flat/interface/expect-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/

type Fn<T> = (<G>() => G extends T ? 1 : 0);

type AreEquals<A, B> = Fn<A> extends Fn<B> ? unknown : never;

/**
* Util for checking two types equality
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function expectType<A, B extends A & AreEquals<A, B>>(): void {}
1 change: 1 addition & 0 deletions src/core/async/modules/flat/interface/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'core/async/modules/flat/interface/promisify';
Loading