Skip to content

Commit f22b9e2

Browse files
samoussHaroenv
authored andcommitted
fix(defer): recover from error (#3933)
* feat(defer): implement waitable callback * fix(defer): recover from callback error * test(defer): remove useless wait test
1 parent 1b9b5f4 commit f22b9e2

File tree

2 files changed

+69
-15
lines changed

2 files changed

+69
-15
lines changed

src/lib/utils/__tests__/defer.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe('defer', () => {
99

1010
expect(fn).toHaveBeenCalledTimes(0);
1111

12-
await Promise.resolve();
12+
await deferred.wait();
1313

1414
expect(fn).toHaveBeenCalledTimes(1);
1515
});
@@ -24,7 +24,7 @@ describe('defer', () => {
2424

2525
expect(fn).toHaveBeenCalledTimes(0);
2626

27-
await Promise.resolve();
27+
await deferred.wait();
2828

2929
expect(fn).toHaveBeenCalledTimes(1);
3030
});
@@ -39,15 +39,15 @@ describe('defer', () => {
3939

4040
expect(fn).toHaveBeenCalledTimes(0);
4141

42-
await Promise.resolve();
42+
await deferred.wait();
4343

4444
expect(fn).toHaveBeenCalledTimes(1);
4545

4646
deferred();
4747
deferred();
4848
deferred();
4949

50-
await Promise.resolve();
50+
await deferred.wait();
5151

5252
expect(fn).toHaveBeenCalledTimes(2);
5353
});
@@ -64,7 +64,7 @@ describe('defer', () => {
6464

6565
expect(fn).toHaveBeenCalledTimes(0);
6666

67-
await Promise.resolve();
67+
await deferred.wait();
6868

6969
expect(fn).toHaveBeenCalledTimes(0);
7070
});
@@ -81,13 +81,13 @@ describe('defer', () => {
8181

8282
expect(fn).toHaveBeenCalledTimes(0);
8383

84-
await Promise.resolve();
84+
await deferred.wait();
8585

8686
expect(fn).toHaveBeenCalledTimes(0);
8787

8888
deferred();
8989

90-
await Promise.resolve();
90+
await deferred.wait();
9191

9292
expect(fn).toHaveBeenCalledTimes(1);
9393
});
@@ -104,8 +104,49 @@ describe('defer', () => {
104104

105105
expect(fn).toHaveBeenCalledTimes(0);
106106

107-
await Promise.resolve();
107+
await deferred.wait();
108108

109109
expect(fn).toHaveBeenCalledTimes(1);
110110
});
111+
112+
it('throws an error when `wait` is called before the deferred function', () => {
113+
const fn = jest.fn();
114+
const deferred = defer(fn);
115+
116+
expect(() => deferred.wait()).toThrowErrorMatchingInlineSnapshot(
117+
`"The deferred function should be called before calling \`wait()\`"`
118+
);
119+
});
120+
121+
it('recovers a deferred function that throws an error', async () => {
122+
const fn = jest.fn();
123+
const deferred = defer(fn);
124+
125+
fn.mockImplementation(() => {
126+
throw new Error('FAIL');
127+
});
128+
129+
deferred();
130+
131+
expect(fn).toHaveBeenCalledTimes(0);
132+
133+
try {
134+
await deferred.wait();
135+
} catch {
136+
// The test verifies that the function is able to recover. We don't want
137+
// to terminate the test on this expected error.
138+
}
139+
140+
expect(fn).toHaveBeenCalledTimes(1);
141+
142+
fn.mockImplementation();
143+
144+
deferred();
145+
146+
expect(fn).toHaveBeenCalledTimes(1);
147+
148+
await deferred.wait();
149+
150+
expect(fn).toHaveBeenCalledTimes(2);
151+
});
111152
});

src/lib/utils/defer.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,48 @@
11
const nextMicroTask = Promise.resolve();
22

33
type Callback = (...args: any[]) => void;
4-
type Cancellable = Callback & {
4+
type Defer = Callback & {
5+
wait(): Promise<void>;
56
cancel(): void;
67
};
78

8-
const defer = (callback: Callback): Cancellable => {
9+
const defer = (callback: Callback): Defer => {
910
let progress: Promise<void> | null = null;
1011
let cancelled = false;
1112

12-
const fn: Cancellable = (...args) => {
13+
const fn: Defer = (...args) => {
1314
if (progress !== null) {
1415
return;
1516
}
1617

1718
progress = nextMicroTask.then(() => {
19+
progress = null;
20+
1821
if (cancelled) {
19-
progress = null;
2022
cancelled = false;
2123
return;
2224
}
2325

2426
callback(...args);
25-
progress = null;
2627
});
2728
};
2829

30+
fn.wait = () => {
31+
if (progress === null) {
32+
throw new Error(
33+
'The deferred function should be called before calling `wait()`'
34+
);
35+
}
36+
37+
return progress;
38+
};
39+
2940
fn.cancel = () => {
30-
if (progress !== null) {
31-
cancelled = true;
41+
if (progress === null) {
42+
return;
3243
}
44+
45+
cancelled = true;
3346
};
3447

3548
return fn;

0 commit comments

Comments
 (0)