diff --git a/.changeset/few-lizards-search.md b/.changeset/few-lizards-search.md new file mode 100644 index 0000000000..12102c7282 --- /dev/null +++ b/.changeset/few-lizards-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/types': minor +--- + +Introduce `createDeferred` and `DeferredPromise`. diff --git a/.changeset/moody-chicken-enjoy.md b/.changeset/moody-chicken-enjoy.md new file mode 100644 index 0000000000..54919557f3 --- /dev/null +++ b/.changeset/moody-chicken-enjoy.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/config-loader': patch +--- + +Internal refactor to use the deferred from the types package diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts index 3cb41a0b48..3c641feadf 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts @@ -26,14 +26,7 @@ import { PluginTaskSchedulerImpl, parseDuration, } from './PluginTaskSchedulerImpl'; - -function defer() { - let resolve = () => {}; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve }; -} +import { createDeferred } from '@backstage/types'; jest.setTimeout(60_000); @@ -188,14 +181,14 @@ describe('PluginTaskManagerImpl', () => { async databaseId => { const { manager } = await init(databaseId); - const { promise, resolve } = defer(); + const promise = createDeferred(); await manager.scheduleTask({ id: 'task1', timeout: Duration.fromMillis(5000), frequency: Duration.fromObject({ years: 1 }), fn: async () => { - resolve(); + promise.resolve(); await new Promise(r => setTimeout(r, 20000)); }, scope: 'global', @@ -313,14 +306,14 @@ describe('PluginTaskManagerImpl', () => { it('cant trigger a running task', async () => { const { manager } = await init('SQLITE_3'); - const { promise, resolve } = defer(); + const promise = createDeferred(); await manager.scheduleTask({ id: 'task1', timeout: Duration.fromMillis(5000), frequency: Duration.fromObject({ years: 1 }), fn: async () => { - resolve(); + promise.resolve(); await new Promise(r => setTimeout(r, 20000)); }, scope: 'local', diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index a8299962b7..8908b4268b 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -20,6 +20,7 @@ import fs from 'fs-extra'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createDeferred } from '@backstage/types'; describe('loadConfig', () => { const mockDir = createMockDirectory({ @@ -298,8 +299,8 @@ describe('loadConfig', () => { }); it('watches config files', async () => { - const onChange = defer(); - const stopSignal = defer(); + const onChange = createDeferred(); + const stopSignal = createDeferred(); await expect( loadConfig({ @@ -307,7 +308,7 @@ describe('loadConfig', () => { configTargets: [], watch: { onChange: onChange.resolve, - stopSignal: stopSignal.promise, + stopSignal, }, }), ).resolves.toEqual({ @@ -331,7 +332,7 @@ describe('loadConfig', () => { title: 'New Title', }, }); - await expect(onChange.promise).resolves.toEqual([ + await expect(onChange).resolves.toEqual([ { context: 'app-config.yaml', data: { @@ -347,8 +348,8 @@ describe('loadConfig', () => { }); it('watches included files', async () => { - const onChange = defer(); - const stopSignal = defer(); + const onChange = createDeferred(); + const stopSignal = createDeferred(); await expect( loadConfig({ @@ -358,7 +359,7 @@ describe('loadConfig', () => { ], watch: { onChange: onChange.resolve, - stopSignal: stopSignal.promise, + stopSignal, }, }), ).resolves.toEqual({ @@ -387,7 +388,7 @@ describe('loadConfig', () => { // via included.yaml await fs.writeFile(mockDir.resolve('secrets/session-key.txt'), 'abc234'); - await expect(onChange.promise).resolves.toEqual([ + await expect(onChange).resolves.toEqual([ { context: 'app-config.development.yaml', data: { @@ -413,8 +414,8 @@ describe('loadConfig', () => { it('watches remote config urls', async () => { server.use(initialLoaderHandler); - const onChange = defer(); - const stopSignal = defer(); + const onChange = createDeferred(); + const stopSignal = createDeferred(); const configUrl = 'https://some.domain.io/app-config.yaml'; await expect( @@ -423,7 +424,7 @@ describe('loadConfig', () => { configTargets: [{ url: configUrl }], watch: { onChange: onChange.resolve, - stopSignal: stopSignal.promise, + stopSignal, }, remote: { reloadIntervalSeconds: 1, @@ -446,7 +447,7 @@ describe('loadConfig', () => { server.use(reloadHandler); - await expect(onChange.promise).resolves.toEqual([ + await expect(onChange).resolves.toEqual([ { context: configUrl, data: { @@ -463,7 +464,7 @@ describe('loadConfig', () => { }); it('stops watching config files', async () => { - const stopSignal = defer(); + const stopSignal = createDeferred(); await loadConfig({ configRoot: mockDir.path, @@ -472,7 +473,7 @@ describe('loadConfig', () => { onChange: () => { expect('not').toBe('called'); }, - stopSignal: stopSignal.promise, + stopSignal, }, }); @@ -496,12 +497,4 @@ describe('loadConfig', () => { appConfigs: [], }); }); - - function defer() { - let resolve: (value: T) => void; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve: resolve! }; - } }); diff --git a/packages/config-loader/src/sources/MutableConfigSource.ts b/packages/config-loader/src/sources/MutableConfigSource.ts index 85585e2882..404a7216d5 100644 --- a/packages/config-loader/src/sources/MutableConfigSource.ts +++ b/packages/config-loader/src/sources/MutableConfigSource.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/types'; +import { DeferredPromise, JsonObject, createDeferred } from '@backstage/types'; import { AsyncConfigSourceGenerator, ConfigSource, ReadConfigDataOptions, } from './types'; -import { simpleDefer, SimpleDeferred, waitOrAbort } from './utils'; +import { waitOrAbort } from './utils'; /** * Options for {@link MutableConfigSource.create}. @@ -52,20 +52,20 @@ export class MutableConfigSource implements ConfigSource { } #currentData?: JsonObject; - #deferred: SimpleDeferred; + #deferred: DeferredPromise; readonly #context: string; readonly #abortController = new AbortController(); private constructor(context: string, initialData?: JsonObject) { this.#currentData = initialData; this.#context = context; - this.#deferred = simpleDefer(); + this.#deferred = createDeferred(); } async *readConfigData( options?: ReadConfigDataOptions | undefined, ): AsyncConfigSourceGenerator { - let deferredPromise = this.#deferred.promise; + let deferredPromise = this.#deferred; if (this.#currentData !== undefined) { yield { configs: [{ data: this.#currentData, context: this.#context }] }; @@ -79,7 +79,7 @@ export class MutableConfigSource implements ConfigSource { if (!ok) { return; } - deferredPromise = this.#deferred.promise; + deferredPromise = this.#deferred; if (this.#currentData !== undefined) { yield { @@ -98,7 +98,7 @@ export class MutableConfigSource implements ConfigSource { if (!this.#abortController.signal.aborted) { this.#currentData = data; const oldDeferred = this.#deferred; - this.#deferred = simpleDefer(); + this.#deferred = createDeferred(); oldDeferred.resolve(); } } diff --git a/packages/config-loader/src/sources/StaticConfigSource.ts b/packages/config-loader/src/sources/StaticConfigSource.ts index 3cbe9771a7..014ead8a99 100644 --- a/packages/config-loader/src/sources/StaticConfigSource.ts +++ b/packages/config-loader/src/sources/StaticConfigSource.ts @@ -14,13 +14,12 @@ * limitations under the License. */ -import { JsonObject, Observable } from '@backstage/types'; +import { JsonObject, Observable, createDeferred } from '@backstage/types'; import { AsyncConfigSourceGenerator, ConfigSource, ReadConfigDataOptions, } from './types'; -import { simpleDefer } from './utils'; /** * Options for {@link StaticConfigSource.create}. @@ -47,13 +46,13 @@ class StaticObservableConfigSource implements ConfigSource { options?: ReadConfigDataOptions | undefined, ): AsyncConfigSourceGenerator { const queue = new Array(); - let deferred = simpleDefer(); + let deferred = createDeferred(); const sub = this.data.subscribe({ next(value) { queue.push(value); deferred.resolve(); - deferred = simpleDefer(); + deferred = createDeferred(); }, complete() { deferred.resolve(); @@ -73,7 +72,7 @@ class StaticObservableConfigSource implements ConfigSource { } for (;;) { - await deferred.promise; + await deferred; if (queue.length === 0) { return; } diff --git a/packages/config-loader/src/sources/utils.ts b/packages/config-loader/src/sources/utils.ts index b948cc47b4..5095364f45 100644 --- a/packages/config-loader/src/sources/utils.ts +++ b/packages/config-loader/src/sources/utils.ts @@ -17,21 +17,6 @@ import { Parser } from './types'; import yaml from 'yaml'; -/** @internal */ -export interface SimpleDeferred { - promise: Promise; - resolve(value: T): void; -} - -/** @internal */ -export function simpleDefer(): SimpleDeferred { - let resolve: (value: T) => void; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve: resolve! }; -} - /** @internal */ export async function waitOrAbort( promise: PromiseLike, diff --git a/packages/types/report.api.md b/packages/types/report.api.md index 9f33bef012..8169a506ef 100644 --- a/packages/types/report.api.md +++ b/packages/types/report.api.md @@ -3,6 +3,21 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +// @public +export function createDeferred< + TResolved = void, + TRejected = Error, +>(): DeferredPromise; + +// @public +export type DeferredPromise< + TResolved = void, + TRejected = Error, +> = Promise & { + resolve(value: TResolved | PromiseLike): void; + reject(reason?: TRejected): void; +}; + // @public export function durationToMilliseconds(duration: HumanDuration): number; diff --git a/packages/types/src/deferred.test.ts b/packages/types/src/deferred.test.ts new file mode 100644 index 0000000000..5180711024 --- /dev/null +++ b/packages/types/src/deferred.test.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDeferred } from './deferred'; + +describe('createDeferred', () => { + it('can both resolve and reject', async () => { + const d1 = createDeferred(); + const d2 = createDeferred(); + const d3 = createDeferred(); + const d4 = createDeferred(); // test the implicit void + + const d1resolved = jest.fn(); + const d1rejected = jest.fn(); + d1.then(d1resolved, d1rejected); + + const d2resolved = jest.fn(); + const d2rejected = jest.fn(); + d2.then(d2resolved, d2rejected); + + const d3resolved = jest.fn(); + const d3rejected = jest.fn(); + // @ts-expect-error wrong argument type for resolved + d3.then(d3resolved, d3rejected); + + const d4resolved = jest.fn(); + const d4rejected = jest.fn(); + // @ts-expect-error resolver should not take arguments for void deferred + d4.then(d4resolved, d4rejected); + + d1.resolve(1); + d1.resolve(2); // should have no effect + + // @ts-expect-error wrong argument type for resolve + d2.resolve(1); + d2.reject(new Error('boom')); // should have no effect + + d3.reject(new Error('boom')); + + // @ts-expect-error void deferred does not take arguments + d4.resolve(1); + + await 'a tick'; + + expect(d1resolved).toHaveBeenCalledTimes(1); + expect(d1resolved).toHaveBeenCalledWith(1); + expect(d1rejected).not.toHaveBeenCalled(); + + expect(d1resolved).toHaveBeenCalledTimes(1); + expect(d2resolved).toHaveBeenCalledWith(1); + expect(d1rejected).not.toHaveBeenCalled(); + + expect(d3resolved).not.toHaveBeenCalled(); + expect(d3rejected).toHaveBeenCalledTimes(1); + expect(d3rejected).toHaveBeenCalledWith( + expect.objectContaining({ message: 'boom' }), + ); + + expect(`${d1}`).toBe('[object DeferredPromise]'); + }); +}); diff --git a/packages/types/src/deferred.ts b/packages/types/src/deferred.ts new file mode 100644 index 0000000000..de17e509a1 --- /dev/null +++ b/packages/types/src/deferred.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A deferred promise that can be resolved or rejected later. + * + * @public + */ +export type DeferredPromise< + TResolved = void, + TRejected = Error, +> = Promise & { + resolve(value: TResolved | PromiseLike): void; + reject(reason?: TRejected): void; +}; + +class Deferred + implements DeferredPromise +{ + #resolve?: (value: TResolved | PromiseLike) => void; + #reject?: (reason?: TRejected) => void; + + public get resolve() { + return this.#resolve!; + } + public get reject() { + return this.#reject!; + } + + public then: Promise['then']; + public catch: Promise['catch']; + public finally: Promise['finally']; + + public constructor() { + const promise = new Promise((resolve, reject) => { + this.#resolve = resolve; + this.#reject = reject; + }); + + this.then = promise.then.bind(promise); + this.catch = promise.catch.bind(promise); + this.finally = promise.finally.bind(promise); + } + + [Symbol.toStringTag]: 'DeferredPromise' = 'DeferredPromise'; +} + +/** + * Creates a deferred promise that can be resolved or rejected later. + * + * @public + */ +export function createDeferred< + TResolved = void, + TRejected = Error, +>(): DeferredPromise { + return new Deferred(); +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 29ba114037..480c85818a 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ +export { createDeferred, type DeferredPromise } from './deferred'; export type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './json'; export type { Observable, Observer, Subscription } from './observable'; export { type HumanDuration, durationToMilliseconds } from './time'; diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts index b26cfe0baf..c322d9adda 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts @@ -24,6 +24,7 @@ import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-node'; +import { createDeferred } from '@backstage/types'; import express from 'express'; import { Knex } from 'knex'; import { Duration } from 'luxon'; @@ -35,7 +36,6 @@ import { IncrementalEntityProvider, IncrementalEntityProviderOptions, } from '../types'; -import { Deferred } from '../util'; /** * Helps in the creation of the catalog entity providers that wrap the @@ -44,7 +44,7 @@ import { Deferred } from '../util'; export class WrapperProviders { private migrate: Promise | undefined; private numberOfProvidersToConnect = 0; - private readonly readySignal = new Deferred(); + private readonly readySignal = createDeferred(); constructor( private readonly options: { diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts b/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts index a31076c6fd..04ceef2d26 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts @@ -20,13 +20,13 @@ import { PluginEnvironment, } from '../types'; import { CatalogBuilder as CoreCatalogBuilder } from '@backstage/plugin-catalog-backend'; +import { createDeferred } from '@backstage/types'; import { Duration } from 'luxon'; import { Knex } from 'knex'; import { IncrementalIngestionEngine } from '../engine/IncrementalIngestionEngine'; import { applyDatabaseMigrations } from '../database/migrations'; import { IncrementalIngestionDatabaseManager } from '../database/IncrementalIngestionDatabaseManager'; import { IncrementalProviderRouter } from '../router/routes'; -import { Deferred } from '../util'; import { EventParams, EventSubscriber } from '@backstage/plugin-events-node'; /** @public */ @@ -43,16 +43,14 @@ export class IncrementalCatalogBuilder { return new IncrementalCatalogBuilder(env, builder, client, manager); } - private ready: Deferred; + private ready = createDeferred(); private constructor( private env: PluginEnvironment, private builder: CoreCatalogBuilder, private client: Knex, private manager: IncrementalIngestionDatabaseManager, - ) { - this.ready = new Deferred(); - } + ) {} async build() { await applyDatabaseMigrations(this.client); diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/util.ts b/plugins/catalog-backend-module-incremental-ingestion/src/util.ts deleted file mode 100644 index d9e820f826..0000000000 --- a/plugins/catalog-backend-module-incremental-ingestion/src/util.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export class Deferred implements Promise { - #resolve?: (value: T) => void; - #reject?: (error: Error) => void; - - public get resolve() { - return this.#resolve!; - } - public get reject() { - return this.#reject!; - } - - public then: Promise['then']; - public catch: Promise['catch']; - public finally: Promise['finally']; - - public constructor() { - const promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - }); - - this.then = promise.then.bind(promise); - this.catch = promise.catch.bind(promise); - this.finally = promise.finally.bind(promise); - } - - [Symbol.toStringTag]: 'Deferred' = 'Deferred'; -} diff --git a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts index 9e515edb5d..0a249a86a9 100644 --- a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts +++ b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts @@ -21,6 +21,7 @@ import { startTestBackend, } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { createDeferred } from '@backstage/types'; import { Knex } from 'knex'; import { applyDatabaseMigrations } from '../../database/migrations'; import { @@ -33,16 +34,6 @@ import { describePerformanceTest, performanceTraceEnabled } from './lib/env'; jest.setTimeout(600_000); -function defer() { - let resolve: (value: T | PromiseLike) => void; - let reject: (error?: unknown) => void; - const promise = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }); - return { promise, resolve: resolve!, reject: reject! }; -} - const traceLog: typeof console.log = performanceTraceEnabled ? console.log : () => {}; @@ -50,7 +41,7 @@ const traceLog: typeof console.log = performanceTraceEnabled class Tracker { private insertBaseEntitiesStart: number | undefined; private insertBaseEntitiesEnd: number | undefined; - private readonly deferred = defer(); + private readonly deferred = createDeferred(); constructor( private readonly knex: Knex, @@ -93,7 +84,7 @@ class Tracker { } async completion(): Promise { - return this.deferred.promise; + return this.deferred; } private completionPolling() { diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx index 3a3816e1e3..33991ff758 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx @@ -15,6 +15,7 @@ */ import { TestApiProvider, renderInTestApp } from '@backstage/test-utils'; +import { createDeferred } from '@backstage/types'; import { screen } from '@testing-library/react'; import React from 'react'; import ObservableImpl from 'zen-observable'; @@ -25,14 +26,6 @@ import { } from '../../apis'; import { EntityDisplayName } from './EntityDisplayName'; -function defer() { - let resolve = (_value: T) => {}; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve }; -} - describe('', () => { const entityPresentationApi = { forEntity: jest.fn(), @@ -63,7 +56,7 @@ describe('', () => { }); it('works with the async the happy path', async () => { - const { promise, resolve } = defer(); + const promise = createDeferred(); entityPresentationApi.forEntity.mockReturnValue({ snapshot: { @@ -89,7 +82,7 @@ describe('', () => { expect(screen.getByText('foo')).toBeInTheDocument(); - resolve({ + promise.resolve({ entityRef: 'component:default/foo', primaryTitle: 'bar', }); diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx index a3230a0cc4..2f8e29a6e3 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx @@ -22,14 +22,7 @@ import React from 'react'; import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState'; import { TestApiProvider } from '@backstage/test-utils'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; - -function defer(): { promise: Promise; resolve: (value: T) => void } { - let resolve: (value: T) => void = () => {}; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve }; -} +import { createDeferred } from '@backstage/types'; describe('useUnregisterEntityDialogState', () => { const catalogApi = catalogApiMock.mock(); @@ -47,15 +40,15 @@ describe('useUnregisterEntityDialogState', () => { beforeEach(() => { jest.resetAllMocks(); - const deferredLocation = defer(); - const deferredColocatedEntities = defer(); + const deferredLocation = createDeferred(); + const deferredColocatedEntities = createDeferred(); resolveLocation = deferredLocation.resolve; resolveColocatedEntities = deferredColocatedEntities.resolve; - catalogApi.getLocationByRef.mockReturnValue(deferredLocation.promise); + catalogApi.getLocationByRef.mockReturnValue(deferredLocation); catalogApi.getEntities.mockReturnValue( - deferredColocatedEntities.promise.then(items => ({ items })), + deferredColocatedEntities.then(items => ({ items })), ); entity = { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 1da2b4b349..2d65488218 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -16,7 +16,12 @@ import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; +import { + JsonObject, + JsonValue, + Observable, + createDeferred, +} from '@backstage/types'; import { Logger } from 'winston'; import ObservableImpl from 'zen-observable'; import { @@ -260,14 +265,6 @@ export interface CurrentClaimedTask { workspace?: Promise; } -function defer() { - let resolve = () => {}; - const promise = new Promise(_resolve => { - resolve = _resolve; - }); - return { promise, resolve }; -} - export class StorageTaskBroker implements TaskBroker { constructor( private readonly storage: TaskStore, @@ -301,7 +298,7 @@ export class StorageTaskBroker implements TaskBroker { return await this.storage.list(options ?? {}); } - private deferredDispatch = defer(); + private deferredDispatch = createDeferred(); private async registerCancellable( taskId: string, @@ -469,12 +466,12 @@ export class StorageTaskBroker implements TaskBroker { } private waitForDispatch() { - return this.deferredDispatch.promise; + return this.deferredDispatch; } private signalDispatch() { this.deferredDispatch.resolve(); - this.deferredDispatch = defer(); + this.deferredDispatch = createDeferred(); } async cancel(taskId: string) {