Skip to content

Commit 1ed8a18

Browse files
authored
fix: Make sure that SyncPromise handler is called only once (getsentry#2511)
* fix: Make sure that SyncPromise handler is called only once * happy linter is happy * linters plz * Use SyncPromise inside Buffer tests
1 parent fd26d9f commit 1ed8a18

File tree

3 files changed

+34
-23
lines changed

3 files changed

+34
-23
lines changed

packages/utils/src/supports.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ export function supportsNativeFetch(): boolean {
106106
// so create a "pure" iframe to see if that has native fetch
107107
let result = false;
108108
const doc = global.document;
109-
if (doc && typeof doc.createElement === 'function') {
109+
// tslint:disable-next-line:no-unbound-method deprecation
110+
if (doc && typeof (doc.createElement as unknown) === `function`) {
110111
try {
111112
const sandbox = doc.createElement('iframe');
112113
sandbox.hidden = true;

packages/utils/src/syncpromise.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ enum States {
1717
class SyncPromise<T> implements PromiseLike<T> {
1818
private _state: States = States.PENDING;
1919
private _handlers: Array<{
20+
done: boolean;
2021
onfulfilled?: ((value: T) => T | PromiseLike<T>) | null;
2122
onrejected?: ((reason: any) => any) | null;
2223
}> = [];
@@ -90,6 +91,7 @@ class SyncPromise<T> implements PromiseLike<T> {
9091
): PromiseLike<TResult1 | TResult2> {
9192
return new SyncPromise((resolve, reject) => {
9293
this._attachHandler({
94+
done: false,
9395
onfulfilled: result => {
9496
if (!onfulfilled) {
9597
// TODO: ¯\_(ツ)_/¯
@@ -156,8 +158,7 @@ class SyncPromise<T> implements PromiseLike<T> {
156158
return;
157159
}
158160

159-
// tslint:disable-next-line:no-unsafe-any
160-
resolve(val);
161+
resolve((val as unknown) as any);
161162
});
162163
});
163164
}
@@ -192,6 +193,8 @@ class SyncPromise<T> implements PromiseLike<T> {
192193
// TODO: FIXME
193194
/** JSDoc */
194195
private readonly _attachHandler = (handler: {
196+
/** JSDoc */
197+
done: boolean;
195198
/** JSDoc */
196199
onfulfilled?(value: T): any;
197200
/** JSDoc */
@@ -207,22 +210,28 @@ class SyncPromise<T> implements PromiseLike<T> {
207210
return;
208211
}
209212

210-
if (this._state === States.REJECTED) {
211-
this._handlers.forEach(handler => {
213+
const cachedHandlers = this._handlers.slice();
214+
this._handlers = [];
215+
216+
cachedHandlers.forEach(handler => {
217+
if (handler.done) {
218+
return;
219+
}
220+
221+
if (this._state === States.RESOLVED) {
222+
if (handler.onfulfilled) {
223+
handler.onfulfilled((this._value as unknown) as any);
224+
}
225+
}
226+
227+
if (this._state === States.REJECTED) {
212228
if (handler.onrejected) {
213229
handler.onrejected(this._value);
214230
}
215-
});
216-
} else {
217-
this._handlers.forEach(handler => {
218-
if (handler.onfulfilled) {
219-
// tslint:disable-next-line:no-unsafe-any
220-
handler.onfulfilled(this._value);
221-
}
222-
});
223-
}
231+
}
224232

225-
this._handlers = [];
233+
handler.done = true;
234+
});
226235
};
227236
}
228237

packages/utils/test/promisebuffer.test.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PromiseBuffer } from '../src/promisebuffer';
2+
import { SyncPromise } from '../src/syncpromise';
23

34
// tslint:disable:no-floating-promises
45

@@ -10,23 +11,23 @@ describe('PromiseBuffer', () => {
1011
describe('add()', () => {
1112
test('no limit', () => {
1213
const q = new PromiseBuffer<void>();
13-
const p = new Promise<void>(resolve => setTimeout(resolve, 1));
14+
const p = new SyncPromise<void>(resolve => setTimeout(resolve, 1));
1415
q.add(p);
1516
expect(q.length()).toBe(1);
1617
});
1718
test('with limit', () => {
1819
const q = new PromiseBuffer<void>(1);
19-
const p = new Promise<void>(resolve => setTimeout(resolve, 1));
20+
const p = new SyncPromise<void>(resolve => setTimeout(resolve, 1));
2021
expect(q.add(p)).toEqual(p);
21-
expect(q.add(new Promise<void>(resolve => setTimeout(resolve, 1)))).rejects.toThrowError();
22+
expect(q.add(new SyncPromise<void>(resolve => setTimeout(resolve, 1)))).rejects.toThrowError();
2223
expect(q.length()).toBe(1);
2324
});
2425
});
2526

2627
test('resolved promises should not show up in buffer length', async () => {
2728
expect.assertions(2);
2829
const q = new PromiseBuffer<void>();
29-
const p = new Promise<void>(resolve => setTimeout(resolve, 1));
30+
const p = new SyncPromise<void>(resolve => setTimeout(resolve, 1));
3031
q.add(p).then(() => {
3132
expect(q.length()).toBe(0);
3233
});
@@ -37,7 +38,7 @@ describe('PromiseBuffer', () => {
3738
test('receive promise result outside and from buffer', async () => {
3839
expect.assertions(4);
3940
const q = new PromiseBuffer<string>();
40-
const p = new Promise<string>(resolve =>
41+
const p = new SyncPromise<string>(resolve =>
4142
setTimeout(() => {
4243
resolve('test');
4344
}, 1),
@@ -57,7 +58,7 @@ describe('PromiseBuffer', () => {
5758
expect.assertions(3);
5859
const q = new PromiseBuffer<void>();
5960
for (let i = 0; i < 5; i++) {
60-
const p = new Promise<void>(resolve => setTimeout(resolve, 1));
61+
const p = new SyncPromise<void>(resolve => setTimeout(resolve, 1));
6162
q.add(p);
6263
}
6364
expect(q.length()).toBe(5);
@@ -72,7 +73,7 @@ describe('PromiseBuffer', () => {
7273
expect.assertions(2);
7374
const q = new PromiseBuffer<void>();
7475
for (let i = 0; i < 5; i++) {
75-
const p = new Promise<void>(resolve => setTimeout(resolve, 100));
76+
const p = new SyncPromise<void>(resolve => setTimeout(resolve, 100));
7677
q.add(p);
7778
}
7879
expect(q.length()).toBe(5);
@@ -96,7 +97,7 @@ describe('PromiseBuffer', () => {
9697
test('rejecting', async () => {
9798
expect.assertions(1);
9899
const q = new PromiseBuffer<void>();
99-
const p = new Promise<void>((_, reject) => setTimeout(reject, 1));
100+
const p = new SyncPromise<void>((_, reject) => setTimeout(reject, 1));
100101
jest.runAllTimers();
101102
return q.add(p).then(null, () => {
102103
expect(true).toBe(true);

0 commit comments

Comments
 (0)