Skip to content

Commit 2eb44fb

Browse files
committed
fix: resolve instead of reject in case of timeout
1 parent db4f00c commit 2eb44fb

File tree

5 files changed

+51
-34
lines changed

5 files changed

+51
-34
lines changed

src/deferred-promise.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
type DeferredPromiseOptions = {
1+
type DeferredPromiseOptions<T> = {
22
timeout?: number;
3+
onTimeout?: (resolve: (value: T) => void, reject: (reason: Error) => void) => void;
34
};
45

56
/** Creates a promise and exposes its resolve and reject methods, with an optional timeout. */
7+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
68
export class DeferredPromise<T> extends Promise<T> {
79
resolve!: (value: T) => void;
810
reject!: (reason: unknown) => void;
911
private timeoutId?: NodeJS.Timeout;
1012

11-
constructor(resolver: (resolve: (value: T) => void, reject: (reason: Error) => void) => void, timeout?: number) {
13+
constructor(
14+
executor: (resolve: (value: T) => void, reject: (reason: Error) => void) => void,
15+
{ timeout, onTimeout }: DeferredPromiseOptions<T> = {}
16+
) {
1217
let resolveFn: (value: T) => void;
1318
let rejectFn: (reason?: unknown) => void;
1419

@@ -24,12 +29,12 @@ export class DeferredPromise<T> extends Promise<T> {
2429

2530
if (timeout !== undefined) {
2631
this.timeoutId = setTimeout(() => {
27-
this.reject(new Error("Promise timed out"));
32+
onTimeout?.(this.resolve, this.reject);
2833
}, timeout);
2934
}
3035

31-
if (resolver) {
32-
resolver(
36+
if (executor) {
37+
executor(
3338
(value: T) => {
3439
if (this.timeoutId) clearTimeout(this.timeoutId);
3540
this.resolve(value);
@@ -42,7 +47,7 @@ export class DeferredPromise<T> extends Promise<T> {
4247
}
4348
}
4449

45-
static fromPromise<T>(promise: Promise<T>, options: DeferredPromiseOptions = {}): DeferredPromise<T> {
50+
static fromPromise<T>(promise: Promise<T>, options: DeferredPromiseOptions<T> = {}): DeferredPromise<T> {
4651
return new DeferredPromise<T>((resolve, reject) => {
4752
promise
4853
.then((value) => {
@@ -51,6 +56,6 @@ export class DeferredPromise<T> extends Promise<T> {
5156
.catch((reason) => {
5257
reject(reason as Error);
5358
});
54-
}, options.timeout);
59+
}, options);
5560
}
5661
}

src/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const LogId = {
1818
telemetryEmitStart: mongoLogId(1_002_003),
1919
telemetryEmitSuccess: mongoLogId(1_002_004),
2020
telemetryDeviceIdFailure: mongoLogId(1_002_005),
21+
telemetryDeviceIdTimeout: mongoLogId(1_002_006),
2122

2223
toolExecute: mongoLogId(1_003_001),
2324
toolExecuteFailure: mongoLogId(1_003_002),

src/telemetry/telemetry.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ export class Telemetry {
3939
}
4040

4141
private async start(): Promise<void> {
42-
this.deviceIdPromise = DeferredPromise.fromPromise(this.getDeviceId());
43-
try {
44-
this.commonProperties.device_id = await this.deviceIdPromise;
45-
} catch (error) {
46-
logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
47-
this.commonProperties.device_id = "unknown";
48-
}
42+
this.deviceIdPromise = DeferredPromise.fromPromise(this.getDeviceId(), {
43+
timeout: DEVICE_ID_TIMEOUT,
44+
onTimeout: (resolve) => {
45+
resolve("unknown");
46+
logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out");
47+
},
48+
});
49+
this.commonProperties.device_id = await this.deviceIdPromise;
4950

5051
this.isBufferingEvents = false;
5152
}
@@ -65,7 +66,7 @@ export class Telemetry {
6566
return this.commonProperties.device_id;
6667
}
6768

68-
const originalId = await DeferredPromise.fromPromise(machineId(true), { timeout: DEVICE_ID_TIMEOUT });
69+
const originalId = await machineId(true);
6970

7071
// Create a hashed format from the all uppercase version of the machine ID
7172
// to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.

tests/unit/deferred-promise.test.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { DeferredPromise } from "../../src/deferred-promise.js";
22

33
describe("DeferredPromise", () => {
4+
beforeEach(() => {
5+
jest.useFakeTimers();
6+
});
7+
afterEach(() => {
8+
jest.useRealTimers();
9+
});
10+
411
it("should resolve with the correct value", async () => {
512
const deferred = new DeferredPromise<string>((resolve) => {
613
resolve("resolved value");
@@ -18,44 +25,47 @@ describe("DeferredPromise", () => {
1825
});
1926

2027
it("should timeout if not resolved or rejected within the specified time", async () => {
21-
const deferred = new DeferredPromise<string>(() => {
22-
// Do not resolve or reject
23-
}, 10);
28+
const deferred = new DeferredPromise<string>(
29+
() => {
30+
// Do not resolve or reject
31+
},
32+
{ timeout: 100, onTimeout: (resolve, reject) => reject(new Error("Promise timed out")) }
33+
);
34+
35+
jest.advanceTimersByTime(100);
2436

2537
await expect(deferred).rejects.toThrow("Promise timed out");
2638
});
2739

2840
it("should clear the timeout when resolved", async () => {
29-
jest.useFakeTimers();
30-
31-
const deferred = new DeferredPromise<string>((resolve) => {
32-
setTimeout(() => resolve("resolved value"), 100);
33-
}, 200);
41+
const deferred = new DeferredPromise<string>(
42+
(resolve) => {
43+
setTimeout(() => resolve("resolved value"), 100);
44+
},
45+
{ timeout: 200 }
46+
);
3447

3548
const promise = deferred.then((value) => {
3649
expect(value).toBe("resolved value");
3750
});
3851

3952
jest.advanceTimersByTime(100);
4053
await promise;
41-
42-
jest.useRealTimers();
4354
});
4455

4556
it("should clear the timeout when rejected", async () => {
46-
jest.useFakeTimers();
47-
48-
const deferred = new DeferredPromise<string>((_, reject) => {
49-
setTimeout(() => reject(new Error("rejected error")), 100);
50-
}, 200);
57+
const deferred = new DeferredPromise<string>(
58+
(_, reject) => {
59+
setTimeout(() => reject(new Error("rejected error")), 100);
60+
},
61+
{ timeout: 200, onTimeout: (resolve, reject) => reject(new Error("Promise timed out")) }
62+
);
5163

5264
const promise = deferred.catch((error) => {
5365
expect(error).toEqual(new Error("rejected error"));
5466
});
5567

5668
jest.advanceTimersByTime(100);
5769
await promise;
58-
59-
jest.useRealTimers();
6070
});
6171
});

tests/unit/telemetry.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,9 @@ describe("Telemetry", () => {
315315
expect(telemetry.getCommonProperties().device_id).toBe("unknown");
316316
expect(telemetry["isBufferingEvents"]).toBe(false);
317317
expect(loggerSpy).toHaveBeenCalledWith(
318-
LogId.telemetryDeviceIdFailure,
318+
LogId.telemetryDeviceIdTimeout,
319319
"telemetry",
320-
"Error: Promise timed out"
320+
"Device ID retrieval timed out"
321321
);
322322
});
323323
});

0 commit comments

Comments
 (0)