-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Export
flattenPromise
helper function
- Loading branch information
Showing
5 changed files
with
140 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# `flattenPromise` | ||
|
||
## Overview | ||
|
||
`flattenPromise` is a utility that makes it easier to work with `Promise<Dependency>` passed into your services. | ||
|
||
For example, it simplifies the implementation of the `TodosRepo` from the [IndexedDB example](./index#usage). | ||
|
||
```ts | ||
function createTodosRepo(idbPromise: Promise<IDBPDatabase>) { | ||
const idb = flattenPromise(idbPromise); // [!code ++] | ||
|
||
return { | ||
async create(todo: Todo): Promise<void> { | ||
await (await idbPromise).add('todos', todo); // [!code --] | ||
await idb.add('todos', todo); // [!code ++] | ||
}, | ||
// ... | ||
}; | ||
} | ||
``` | ||
|
||
It works by using a `Proxy` to await the promise internally before calling any methods. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { flattenPromise } from './flattenPromise'; | ||
|
||
describe('flattenPromise', () => { | ||
it('should convert Promise<Function> to DeepAsync<Function>', async () => { | ||
const fnPromise = Promise.resolve((x: number, y: number) => x + y); | ||
|
||
const fn = flattenPromise(fnPromise); | ||
const actual = await fn(1, 2); | ||
|
||
expect(actual).toBe(3); | ||
}); | ||
|
||
it('should convert shallow Promise<Object> to DeepAsync<Object>', async () => { | ||
const objectPromise = Promise.resolve({ | ||
additionalIncrement: 1, | ||
add(x: number, y: number): number { | ||
return x + y + this.additionalIncrement; | ||
}, | ||
}); | ||
|
||
const object = flattenPromise(objectPromise); | ||
const actual = await object.add(1, 2); | ||
|
||
expect(actual).toBe(4); | ||
}); | ||
|
||
it('should convert nested Promise<Object> to DeepAsync<Object>', async () => { | ||
const objectPromise = Promise.resolve({ | ||
math: { | ||
additionalIncrement: 1, | ||
add(x: number, y: number): number { | ||
return x + y + this.additionalIncrement; | ||
}, | ||
}, | ||
}); | ||
|
||
const object = flattenPromise(objectPromise); | ||
const actual = await object.math.add(1, 2); | ||
|
||
expect(actual).toBe(4); | ||
}); | ||
|
||
it('should convert Promise<Class> to DeepAsync<Class>', async () => { | ||
const instancePromise = Promise.resolve( | ||
new (class { | ||
additionalIncrement = 1; | ||
add(x: number, y: number): number { | ||
return x + y + this.additionalIncrement; | ||
} | ||
})(), | ||
); | ||
|
||
const instance = flattenPromise(instancePromise); | ||
const actual = await instance.add(1, 2); | ||
|
||
expect(actual).toBe(4); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import get from 'get-value'; | ||
import { DeepAsync } from './types'; | ||
|
||
/** | ||
* Given a promise of a variable, return a proxy to that variable that awaits the promise internally | ||
* so you don't have to call `await` twice. | ||
* | ||
* You can unwrap promises of functions, objects, or classes. | ||
* | ||
* This is meant to be used to simplify service implementations, like so: | ||
* | ||
* @example | ||
* function createService(dependencyPromise: Promise<SomeDependency>) { | ||
* const dependency = flattenPromise(dependencyPromise); | ||
* | ||
* return { | ||
* doSomething() { | ||
* await dependency.someAsyncWork(); | ||
* // Instead of `await (await dependencyPromise).someAsyncWork();` | ||
* } | ||
* } | ||
* } | ||
*/ | ||
export function flattenPromise<T>(promise: Promise<T>): DeepAsync<T> { | ||
function createProxy(location?: { propertyPath: string; parentPath?: string }): DeepAsync<T> { | ||
const wrapped = (() => {}) as DeepAsync<T>; | ||
const proxy = new Proxy(wrapped, { | ||
async apply(_target, _thisArg, args) { | ||
const t = (await promise) as any; | ||
const thisArg = (location?.parentPath ? get(t, location.parentPath) : t) as any | undefined; | ||
const fn = (location ? get(t, location.propertyPath) : t) as (...args: any[]) => any; | ||
return fn.apply(thisArg, args); | ||
}, | ||
|
||
// Executed when accessing a property on an object | ||
get(target, propertyName, receiver) { | ||
if (propertyName === '__proxy' || typeof propertyName === 'symbol') { | ||
return Reflect.get(target, propertyName, receiver); | ||
} | ||
return createProxy({ | ||
propertyPath: | ||
location == null ? propertyName : `${location.propertyPath}.${propertyName}`, | ||
parentPath: location?.propertyPath, | ||
}); | ||
}, | ||
}); | ||
// @ts-expect-error: Adding a hidden property | ||
proxy.__proxy = true; | ||
return proxy; | ||
} | ||
|
||
return createProxy(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export { defineProxyService } from './defineProxyService'; | ||
export type { ProxyServiceConfig } from './types'; | ||
export type { ProxyServiceConfig, DeepAsync } from './types'; |