From 072a9c2314406da5beada670885cc9aa175bac28 Mon Sep 17 00:00:00 2001 From: Dmitry Karpunin Date: Sun, 10 Dec 2023 21:11:01 +0300 Subject: [PATCH] Remove promise.withresolvers; eslint --- .eslint-configs/base.json | 53 ++++++++++++ .eslint-configs/typescript.json | 102 +++++++++++++++++++++++ .eslint-configs/unicorn.json | 50 +++++++++++ .eslintrc.json | 29 +++++++ package.json | 10 +-- src/BatchLoader.spec.ts | 23 ++--- src/BatchLoader.ts | 70 ++++++++-------- src/BatchLoader.types.ts | 14 ++-- src/ImmutableBatchLoaderItemsStore.ts | 6 +- src/index.ts | 4 +- src/itemsStores.spec.ts | 38 ++++----- src/promiseWithResolvers.ts | 35 ++++++++ tsconfig.build.json | 1 - typings/promise.withresolvers/index.d.ts | 21 ----- 14 files changed, 352 insertions(+), 104 deletions(-) create mode 100644 .eslint-configs/base.json create mode 100644 .eslint-configs/typescript.json create mode 100644 .eslint-configs/unicorn.json create mode 100644 .eslintrc.json create mode 100644 src/promiseWithResolvers.ts delete mode 100644 typings/promise.withresolvers/index.d.ts diff --git a/.eslint-configs/base.json b/.eslint-configs/base.json new file mode 100644 index 0000000..4ca2146 --- /dev/null +++ b/.eslint-configs/base.json @@ -0,0 +1,53 @@ +{ + "extends": [ + "eslint:all" + ], + "rules": { + "array-element-newline": ["error", "consistent"], + "capitalized-comments": "off", + "class-methods-use-this": "off", + "comma-dangle": ["error", "always-multiline"], + "consistent-return": "off", + "curly": ["error", "multi-line"], + "default-case": "off", + "dot-location": ["error", "property"], + "eol-last": "error", + "func-style": ["error", "declaration", { "allowArrowFunctions": true }], + "function-call-argument-newline": ["error", "consistent"], + "function-paren-newline": ["error", "multiline-arguments"], + "id-length": ["error", { "exceptions": ["q", "x", "y"] }], + "indent": ["error", 2, { "SwitchCase": 1 }], + "line-comment-position": "off", + "multiline-comment-style": "off", + "multiline-ternary": "off", + "no-console": "error", + "no-duplicate-imports": "off", + "no-inline-comments": "off", + "no-mixed-operators": "off", + "no-multi-spaces": "error", + "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0, "maxBOF": 0 }], + "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }], + "no-ternary": "off", + "no-trailing-spaces": "error", + "no-undefined": "off", + "no-use-before-define": ["error", { "functions": false }], + "no-void": "off", + "no-warning-comments": "off", + "object-curly-spacing": ["error", "always"], + "object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }], + "one-var": ["error", "never"], + "operator-linebreak": ["error", "before", { + "overrides": { + "=": "after" + } + }], + "padded-blocks": ["error", "never"], + "quote-props": ["error", "consistent-as-needed"], + "quotes": ["error", "single"], + "require-unicode-regexp": "off", + "semi": ["error", "never"], + "sort-imports": "off", + "sort-keys": "off", + "spaced-comment": "off" + } +} diff --git a/.eslint-configs/typescript.json b/.eslint-configs/typescript.json new file mode 100644 index 0000000..ea4f527 --- /dev/null +++ b/.eslint-configs/typescript.json @@ -0,0 +1,102 @@ +{ + "extends": [ + "plugin:@typescript-eslint/all" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "parserOptions": { + "project": "./tsconfig.json" + }, + "rules": { + "@typescript-eslint/ban-types": ["error", { + "types": { + "{}": false + } + }], + "@typescript-eslint/comma-dangle": ["error", "always-multiline"], + "@typescript-eslint/comma-spacing": "error", + "@typescript-eslint/consistent-type-imports": ["error", { + "prefer": "type-imports", + "disallowTypeAnnotations": false + }], + "@typescript-eslint/explicit-member-accessibility": ["error", { "accessibility": "no-public" }], + "@typescript-eslint/indent": ["error", 2, { + "SwitchCase": 1, + "ignoredNodes": [ + "FunctionExpression > .params[decorators.length > 0]", + "FunctionExpression > .params > :matches(Decorator, :not(:first-child))", + "ClassBody.body > PropertyDefinition[decorators.length > 0] > .key", + "TSTypeParameterInstantiation", + "TSIntersectionType" + ] + }], + "@typescript-eslint/init-declarations": "off", + "@typescript-eslint/lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], + "max-params": "off", + "@typescript-eslint/max-params": ["error", { "max": 5 }], + "@typescript-eslint/member-delimiter-style": ["error", { + "multiline": { + "delimiter": "none", + "requireLast": true + }, + "singleline": { + "delimiter": "comma", + "requireLast": false + }, + "multilineDetection": "brackets" + }], + "@typescript-eslint/member-ordering": "off", + "@typescript-eslint/no-invalid-void-type": "off", + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksConditionals": true, + "checksVoidReturn": { + "arguments": true, + "attributes": false, + "properties": true, + "returns": true, + "variables": true + } + } + ], + "@typescript-eslint/no-type-alias": "off", + "@typescript-eslint/no-unused-vars": ["error", { + "varsIgnorePattern": "^_", + "argsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_" + }], + "@typescript-eslint/no-use-before-define": ["error", { + "functions": false, + "classes": true, + "variables": true, + "enums": true, + "typedefs": true, + "ignoreTypeReferences": true + }], + "@typescript-eslint/object-curly-spacing": ["error", "always"], + "@typescript-eslint/parameter-properties": ["error", { "prefer": "parameter-property" }], + "prefer-destructuring": "off", + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/prefer-readonly-parameter-types": "off", + "@typescript-eslint/promise-function-async": "off", + "@typescript-eslint/quotes": ["error", "single"], + "@typescript-eslint/no-extra-parens": "off", + "@typescript-eslint/no-magic-numbers": ["error", { + "ignore": [ + 0 + ], + "ignoreArrayIndexes": true, + "detectObjects": false + }], + "@typescript-eslint/semi": ["error", "never"], + "@typescript-eslint/sort-type-constituents": "off", + "@typescript-eslint/sort-type-union-intersection-members": "off", + "@typescript-eslint/space-before-function-paren": ["error", { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + }], + "@typescript-eslint/strict-boolean-expressions": "off" + } +} diff --git a/.eslint-configs/unicorn.json b/.eslint-configs/unicorn.json new file mode 100644 index 0000000..471cbd6 --- /dev/null +++ b/.eslint-configs/unicorn.json @@ -0,0 +1,50 @@ +{ + "extends": [ + "plugin:unicorn/all" + ], + "rules": { + "unicorn/filename-case": ["error", { + "cases": { + "camelCase": true, + "pascalCase": true + }, + "ignore": [ + "\\.d\\.ts$" + ] + }], + "unicorn/no-array-callback-reference": "off", + "unicorn/no-keyword-prefix": "off", + "unicorn/no-nested-ternary": "off", + "unicorn/no-null": "off", + "unicorn/no-useless-undefined": "off", + "unicorn/numeric-separators-style": ["error", { + "number": { + "minimumDigits": 4 + } + }], + "unicorn/prefer-top-level-await": "off", + "unicorn/prevent-abbreviations": ["error", { + "replacements": { + "args": false, + "db": false, + "dev": false, + "dist": false, + "e": false, + "env": false, + "err": false, + "fn": false, + "func": false, + "params": false, + "prev": false, + "prop": false, + "props": false, + "ref": false, + "req": false, + "res": false + }, + "ignore": [ + "prevState" + ] + }] + } +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..cc3aa2a --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "root": true, + "extends": [ + "./.eslint-configs/base.json", + "./.eslint-configs/unicorn.json" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx" + ], + "extends": [ + "./.eslint-configs/typescript.json" + ] + }, + { + "files": [ + "dist/*.js" + ], + "rules": { + "init-declarations": "off", + "no-magic-numbers": "off", + "no-undef": "off", + "no-unused-vars": "off" + } + } + ] +} diff --git a/package.json b/package.json index 6b8c249..70e4433 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "files": [ "dist", "src", - "typings", "package.json", "README.md", "LICENSE" @@ -25,23 +24,24 @@ }, "scripts": { "ts-check": "tsc --noEmit --incremental", + "lint": "eslint src --fix", + "lint:ci": "eslint src", "test": "jest", "test:ci": "jest --ci", + "precommit": "npm run lint && npm run ts-check && npm run lint", "prebuild": "rm -rf ./dist", "build": "tsc --project tsconfig.build.json", + "postbuild": "eslint dist --fix", "prerelease": "npm run build", "release": "np" }, - "dependencies": { - "promise.withresolvers": "^1.0.1" - }, + "devDependencies": { "@total-typescript/ts-reset": "^0.5.1", "@types/jest": "^29.5.11", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "eslint": "^8.55.0", - "eslint-plugin-react-perf": "^3.3.1", "eslint-plugin-unicorn": "^49.0.0", "jest": "^29.7.0", "np": "^9.2.0", diff --git a/src/BatchLoader.spec.ts b/src/BatchLoader.spec.ts index 48c1a59..14add65 100644 --- a/src/BatchLoader.spec.ts +++ b/src/BatchLoader.spec.ts @@ -1,10 +1,11 @@ -import type { IBatchLoaderItem, IBatchLoaderOptions } from './BatchLoader.types' +/* eslint-disable max-lines, max-lines-per-function, max-statements, @typescript-eslint/no-magic-numbers */ +import type { BatchLoaderStatus, IBatchLoaderItem, IBatchLoaderOptions } from './BatchLoader.types' import BatchLoader from './BatchLoader' import ImmutableBatchLoaderItemsStore from './ImmutableBatchLoaderItemsStore' const TEST_TIMEOUT = 1 -function timeout(delay = 0) { +function timeout(delay = 0): Promise { return new Promise((resolve) => { setTimeout(resolve, delay) }) @@ -20,7 +21,7 @@ describe('BatchLoader', () => { resolve( ids.map((id) => ({ test: `test_${id}_${salt}`, - })) + })), ) }, TEST_TIMEOUT) }), @@ -114,7 +115,7 @@ describe('BatchLoader', () => { () => state, (newState) => { state = newState - } + }, ) const itemsStoreSpies = { @@ -131,7 +132,7 @@ describe('BatchLoader', () => { resolve( ids.map((id) => ({ test: `test_${id}_${salt}`, - })) + })), ) }, TEST_TIMEOUT) }), @@ -230,7 +231,7 @@ describe('BatchLoader', () => { ]) }) - it("should work with default refetchStrategy ('unfetched')", async () => { + it('should work with default refetchStrategy (\'unfetched\')', async () => { let salt = '1' const options: IBatchLoaderOptions = { @@ -239,14 +240,14 @@ describe('BatchLoader', () => { resolve( ids.map((id) => ({ test: `test_${id}_${salt}`, - })) + })), ) }, TEST_TIMEOUT) }), } const batchFetchSpy = jest.spyOn(options, 'batchFetch') - const testBatchLoader = new BatchLoader(options) + const testBatchLoader = new BatchLoader(options) as BatchLoader & { batchStatus: BatchLoaderStatus } expect(testBatchLoader.batchStatus).toStrictEqual('unrequested') expect(testBatchLoader.getState('a')).toStrictEqual({ @@ -325,7 +326,7 @@ describe('BatchLoader', () => { expect(batchFetchSpy).toHaveBeenNthCalledWith(2, ['c']) }) - it("should work with refetchStrategy: 'refresh'", async () => { + it('should work with refetchStrategy: \'refresh\'', async () => { let salt = '1' const options: IBatchLoaderOptions = { @@ -334,7 +335,7 @@ describe('BatchLoader', () => { resolve( ids.map((id) => ({ test: `test_${id}_${salt}`, - })) + })), ) }, TEST_TIMEOUT) }), @@ -342,7 +343,7 @@ describe('BatchLoader', () => { } const batchFetchSpy = jest.spyOn(options, 'batchFetch') - const testBatchLoader = new BatchLoader(options) + const testBatchLoader = new BatchLoader(options) as BatchLoader & { batchStatus: BatchLoaderStatus } expect(testBatchLoader.batchStatus).toStrictEqual('unrequested') expect(testBatchLoader.getState('a')).toStrictEqual({ diff --git a/src/BatchLoader.ts b/src/BatchLoader.ts index a87243a..8bb8c8c 100644 --- a/src/BatchLoader.ts +++ b/src/BatchLoader.ts @@ -1,7 +1,3 @@ -import withResolvers from 'promise.withresolvers' - -withResolvers.shim() - import type { BatchLoaderStatus, IBatchLoaderGetStateResult, @@ -11,8 +7,10 @@ import type { IBatchLoaderOptions, } from './BatchLoader.types' import DefaultBatchLoaderItemsStore from './DefaultBatchLoaderItemsStore' +import type { PromiseWithResolvers } from './promiseWithResolvers' +import promiseWithResolvers from './promiseWithResolvers' -function defaultBatchScheduleFn(callback: () => void): void { +function defaultBatchScheduleFn(this: void, callback: () => void): void { setTimeout(callback) } @@ -22,9 +20,9 @@ const fetchingItemPatch: IBatchLoaderItemPatch = Object.freeze({ export default class BatchLoader { private readonly batchScheduleFn: (callback: () => void) => void - private readonly itemsStore: IBatchLoaderItemsStore - public batchStatus: BatchLoaderStatus = 'unrequested' - private batchBuffer: ID[] = [] + protected readonly itemsStore: IBatchLoaderItemsStore + protected batchStatus: BatchLoaderStatus = 'unrequested' + protected batchBuffer: ID[] = [] constructor(private readonly options: IBatchLoaderOptions) { this.batchScheduleFn = options.batchScheduleFn || defaultBatchScheduleFn @@ -94,9 +92,9 @@ export default class BatchLoader { this.scheduleBatchFetch() } - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return Promise.all( - ids.map((id) => this.itemsStore.get(id)!.deferred.promise) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + ids.map((id) => this.itemsStore.get(id)!.deferred.promise), ) as Promise<{ -readonly [I in keyof IDS]: R }> } @@ -108,7 +106,7 @@ export default class BatchLoader { } private scheduleItem(id: ID, item?: IBatchLoaderItem): PromiseWithResolvers { - const deferred = Promise.withResolvers() + const deferred = promiseWithResolvers() const newItem = { deferred, @@ -126,7 +124,7 @@ export default class BatchLoader { return deferred } - private scheduleBatchFetch() { + private scheduleBatchFetch(): void { if (this.batchStatus === 'scheduled' || this.batchStatus === 'fetching') { return } @@ -144,6 +142,7 @@ export default class BatchLoader { void this.doFetch(batchBuffer).then((batchStatus) => { this.batchStatus = batchStatus + // eslint-disable-next-line unicorn/consistent-destructuring if (this.batchBuffer.length > 0) { this.scheduleBatchFetch() } @@ -152,39 +151,43 @@ export default class BatchLoader { } private async doFetch(ids: ID[]): Promise<'resolved' | 'rejected'> { - let results: (R | Error | null | undefined)[] - this.itemsStore.batchUpdate( - ids.map((id) => [id, fetchingItemPatch as IBatchLoaderItemPatch]) + ids.map((id) => [id, fetchingItemPatch as IBatchLoaderItemPatch]), ) + let results: (R | Error | null | undefined)[] + try { results = await this.options.batchFetch(ids) } catch (error: unknown) { - const patch: IBatchLoaderItemPatch = { - status: 'rejected', - error, - } + this.applyBatchError(ids, error) + return 'rejected' + } + + this.applyBatchResult(ids, results) + return 'resolved' + } - const items = this.itemsStore.batchUpdate(ids.map((id) => [id, patch])) + private applyBatchError(ids: ID[], error: unknown): void { + const patch: IBatchLoaderItemPatch = { + status: 'rejected', + error, + } - for (const item of items) { - item.deferred.reject(error) - } + const items = this.itemsStore.batchUpdate(ids.map((id) => [id, patch])) - this.options.onError?.(error) - return 'rejected' + for (const item of items) { + item.deferred.reject(error) } + this.options.onError?.(error) + } + + private applyBatchResult(ids: ID[], results: (R | Error | null | undefined)[]): void { const items = this.itemsStore.batchUpdate( + // TODO: maybe dont erase prev values by `|| undefined` // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - ids - .map((id, index) => { - const result = results[index] - - return this.createBatchUpdateEntry(id, result || undefined) - }) - // .filter(Boolean) as [ID, IBatchLoaderItemPatch][] + ids.map((id, index) => this.createBatchUpdateEntry(id, results[index] || undefined)), ) for (const { deferred, result, error } of items) { @@ -195,10 +198,9 @@ export default class BatchLoader { deferred.resolve(result!) } } - - return 'resolved' } + // eslint-disable-next-line @typescript-eslint/class-methods-use-this private createBatchUpdateEntry(id: ID, result: R | Error | undefined): [ID, IBatchLoaderItemPatch] { return [ id, diff --git a/src/BatchLoader.types.ts b/src/BatchLoader.types.ts index 0ccec46..ea80bd4 100644 --- a/src/BatchLoader.types.ts +++ b/src/BatchLoader.types.ts @@ -1,9 +1,11 @@ +import type { PromiseWithResolvers } from './promiseWithResolvers' + export interface IBatchLoaderOptions { batchFetch: BatchFetchFunction - batchScheduleFn?(callback: () => void): void + batchScheduleFn?: (this: void, callback: () => void) => void itemsStore?: IBatchLoaderItemsStore refetchStrategy?: 'unfetched' | 'refresh' - onError?(error: unknown): void + onError?: (error: unknown) => void } type BatchFetchFunction = ( @@ -11,10 +13,10 @@ type BatchFetchFunction = ( ) => Promise<(R | Error | null | undefined)[]> export interface IBatchLoaderItemsStore { - get(id: ID): IBatchLoaderItem | undefined - add(id: ID, item: IBatchLoaderItem): void - update(id: ID, patch: IBatchLoaderItemPatch): IBatchLoaderItem - batchUpdate(entries: [ID, IBatchLoaderItemPatch][]): IBatchLoaderItem[] + get: (id: ID) => IBatchLoaderItem | undefined + add: (id: ID, item: IBatchLoaderItem) => void + update: (id: ID, patch: IBatchLoaderItemPatch) => IBatchLoaderItem + batchUpdate: (entries: [ID, IBatchLoaderItemPatch][]) => IBatchLoaderItem[] } export type BatchLoaderStatus = diff --git a/src/ImmutableBatchLoaderItemsStore.ts b/src/ImmutableBatchLoaderItemsStore.ts index a31bca8..62f2485 100644 --- a/src/ImmutableBatchLoaderItemsStore.ts +++ b/src/ImmutableBatchLoaderItemsStore.ts @@ -13,7 +13,7 @@ export default class ImmutableBatchLoaderItemsStore { constructor( private readonly getState: () => ImmutableBatchLoaderItemsStoreState, - private readonly setState: (state: ImmutableBatchLoaderItemsStoreState) => void + private readonly setState: (state: ImmutableBatchLoaderItemsStoreState) => void, ) {} get(id: ID): IBatchLoaderItem { @@ -29,7 +29,7 @@ implements IBatchLoaderItemsStore { update(id: ID, patch: IBatchLoaderItemPatch): IBatchLoaderItem { const state = this.getState() - const item: IBatchLoaderItem | undefined = state[id] + const item = state[id] as IBatchLoaderItem | undefined if (!item) { throw new Error(`Item with id: ${JSON.stringify(id)} not found`) @@ -54,7 +54,7 @@ implements IBatchLoaderItemsStore { const patchedEntries = entries .map(([id, patch]) => { - const item: IBatchLoaderItem | undefined = state[id] + const item = state[id] as IBatchLoaderItem | undefined if (!item) { notFoundIds.push(id) diff --git a/src/index.ts b/src/index.ts index 6f0d244..849b506 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1 @@ -import BatchLoader from './BatchLoader' - -export default BatchLoader +export { default } from './BatchLoader' diff --git a/src/itemsStores.spec.ts b/src/itemsStores.spec.ts index 129e46b..8ce8266 100644 --- a/src/itemsStores.spec.ts +++ b/src/itemsStores.spec.ts @@ -1,10 +1,8 @@ -import withResolvers from 'promise.withresolvers' - -withResolvers.shim() - +/* eslint-disable max-lines-per-function, max-statements */ import DefaultBatchLoaderItemsStore from './DefaultBatchLoaderItemsStore' import ImmutableBatchLoaderItemsStore from './ImmutableBatchLoaderItemsStore' import type { IBatchLoaderItem } from './BatchLoader.types' +import promiseWithResolvers from './promiseWithResolvers' describe('items stores', () => { describe('DefaultBatchLoaderItemsStore', () => { @@ -14,17 +12,17 @@ describe('items stores', () => { expect(itemsStore.get('a')).toBeUndefined() const item = { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', } as const itemsStore.add('a', item) itemsStore.add('b', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) itemsStore.add('d', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) @@ -79,23 +77,23 @@ describe('items stores', () => { const itemsStore = new DefaultBatchLoaderItemsStore() const item = { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', } as const itemsStore.add('a', item) itemsStore.add('b', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) itemsStore.add('d', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) // Updating not existing item throws error expect(() => itemsStore.update('c', { status: 'fetching' })).toThrowError( - 'Item with id: "c" not found' + 'Item with id: "c" not found', ) // Updating not existing item throws error, but set all existing items @@ -118,23 +116,23 @@ describe('items stores', () => { () => state, (newState) => { state = newState - } + }, ) expect(itemsStore.get('a')).toBeUndefined() const item = { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', } as const itemsStore.add('a', item) itemsStore.add('b', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) itemsStore.add('d', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) @@ -192,27 +190,27 @@ describe('items stores', () => { () => state, (newState) => { state = newState - } + }, ) const item = { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', } as const itemsStore.add('a', item) itemsStore.add('b', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) itemsStore.add('d', { - deferred: Promise.withResolvers<{ test: string }>(), + deferred: promiseWithResolvers<{ test: string }>(), status: 'scheduled', }) // Updating not existing item throws error expect(() => itemsStore.update('c', { status: 'fetching' })).toThrowError( - 'Item with id: "c" not found' + 'Item with id: "c" not found', ) // Updating not existing item throws error, but set all existing items diff --git a/src/promiseWithResolvers.ts b/src/promiseWithResolvers.ts new file mode 100644 index 0000000..b681db6 --- /dev/null +++ b/src/promiseWithResolvers.ts @@ -0,0 +1,35 @@ +export interface PromiseWithResolvers { + promise: Promise + resolve: (value: T) => void + reject: (reason: unknown) => void +} + +interface PromiseWithMethodWithResolvers { + withResolvers: () => PromiseWithResolvers +} + +export default function promiseWithResolvers(): PromiseWithResolvers { + return 'withResolvers' in Promise + + /* c8 ignore next */ + ? (Promise as PromiseWithMethodWithResolvers).withResolvers() + : createPromiseWithResolvers() +} + +function createPromiseWithResolvers(): PromiseWithResolvers { + let resolve: (value: T | PromiseLike) => void + let reject: (reason?: unknown) => void + + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) + + return { + promise, + // @ts-expect-error resolve assigned + resolve, + // @ts-expect-error reject assigned + reject, + } +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 8a3dbef..c24c61c 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,7 +1,6 @@ { "extends": "./tsconfig.json", "include": [ - "typings/**/*.ts", "src/**/*.ts" ], "exclude": [ diff --git a/typings/promise.withresolvers/index.d.ts b/typings/promise.withresolvers/index.d.ts deleted file mode 100644 index 812bfe1..0000000 --- a/typings/promise.withresolvers/index.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -interface PromiseWithResolvers { - promise: Promise - resolve: (value: T) => void - reject: (reason: unknown) => void -} - -interface PromiseConstructor { - withResolvers(): PromiseWithResolvers -} - -declare module 'promise.withresolvers' { - - type WithResolvers = { - (): void - shim: () => void - } - - declare const withResolvers: WithResolvers - - export default withResolvers -}