Skip to content

Commit e07ed7a

Browse files
committed
cache errors
1 parent 8cfab2c commit e07ed7a

File tree

6 files changed

+127
-31
lines changed

6 files changed

+127
-31
lines changed

src/jsutils/__tests__/withCache-test.ts

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { expect } from 'chai';
22
import { describe, it } from 'mocha';
33

4+
import { expectPromise } from '../../__testUtils__/expectPromise.js';
45
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js';
56

67
import { isPromise } from '../isPromise.js';
78
import { withCache } from '../withCache.js';
89

910
describe('withCache', () => {
1011
it('returns asynchronously using asynchronous cache', async () => {
11-
let cached: string | undefined;
12+
let cached: string | Error | undefined;
1213
let getAttempts = 0;
1314
let cacheHits = 0;
1415
const customCache = {
15-
set: async (result: string) => {
16+
set: async (result: string | Error) => {
1617
await resolveOnNextTick();
1718
cached = result;
1819
},
@@ -46,11 +47,11 @@ describe('withCache', () => {
4647
});
4748

4849
it('returns synchronously using cache with sync getter and async setter', async () => {
49-
let cached: string | undefined;
50+
let cached: string | Error | undefined;
5051
let getAttempts = 0;
5152
let cacheHits = 0;
5253
const customCache = {
53-
set: async (result: string) => {
54+
set: async (result: string | Error) => {
5455
await resolveOnNextTick();
5556
cached = result;
5657
},
@@ -80,11 +81,11 @@ describe('withCache', () => {
8081
});
8182

8283
it('returns asynchronously using cache with async getter and sync setter', async () => {
83-
let cached: string | undefined;
84+
let cached: string | Error | undefined;
8485
let getAttempts = 0;
8586
let cacheHits = 0;
8687
const customCache = {
87-
set: (result: string) => {
88+
set: (result: string | Error) => {
8889
cached = result;
8990
},
9091
get: () => {
@@ -151,4 +152,74 @@ describe('withCache', () => {
151152
expect(getAttempts).to.equal(2);
152153
expect(cacheHits).to.equal(0);
153154
});
155+
156+
it('caches fn errors with sync cache', () => {
157+
let cached: string | Error | undefined;
158+
let getAttempts = 0;
159+
let cacheHits = 0;
160+
const customCache = {
161+
set: (result: string | Error) => {
162+
cached = result;
163+
},
164+
get: () => {
165+
getAttempts++;
166+
if (cached !== undefined) {
167+
cacheHits++;
168+
}
169+
return cached;
170+
},
171+
};
172+
173+
const fnWithCache = withCache((): string => {
174+
throw new Error('Oops');
175+
}, customCache);
176+
177+
expect(() => fnWithCache()).to.throw('Oops');
178+
179+
expect(getAttempts).to.equal(1);
180+
expect(cacheHits).to.equal(0);
181+
182+
expect(() => fnWithCache()).to.throw('Oops');
183+
184+
expect(getAttempts).to.equal(2);
185+
expect(cacheHits).to.equal(1);
186+
});
187+
188+
it('caches fn errors with async cache', async () => {
189+
let cached: string | Error | undefined;
190+
let getAttempts = 0;
191+
let cacheHits = 0;
192+
const customCache = {
193+
set: async (result: string | Error) => {
194+
await resolveOnNextTick();
195+
cached = result;
196+
},
197+
get: () => {
198+
getAttempts++;
199+
if (cached !== undefined) {
200+
cacheHits++;
201+
}
202+
return Promise.resolve(cached);
203+
},
204+
};
205+
206+
const fnWithCache = withCache((): string => {
207+
throw new Error('Oops');
208+
}, customCache);
209+
210+
const firstResultPromise = fnWithCache();
211+
expect(isPromise(firstResultPromise)).to.equal(true);
212+
213+
await expectPromise(firstResultPromise).toRejectWith('Oops');
214+
expect(getAttempts).to.equal(1);
215+
expect(cacheHits).to.equal(0);
216+
217+
const secondResultPromise = fnWithCache();
218+
219+
expect(isPromise(secondResultPromise)).to.equal(true);
220+
221+
await expectPromise(secondResultPromise).toRejectWith('Oops');
222+
expect(getAttempts).to.equal(2);
223+
expect(cacheHits).to.equal(1);
224+
});
154225
});

src/jsutils/withCache.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@ import { isPromise } from './isPromise.js';
22
import type { PromiseOrValue } from './PromiseOrValue.js';
33

44
export interface FnCache<
5-
T extends (...args: Array<any>) => Exclude<any, undefined>,
5+
T extends (...args: Array<any>) => Exclude<any, Error | undefined>,
66
> {
7-
set: (result: ReturnType<T>, ...args: Parameters<T>) => PromiseOrValue<void>;
8-
get: (...args: Parameters<T>) => PromiseOrValue<ReturnType<T> | undefined>;
7+
set: (
8+
result: ReturnType<T> | Error,
9+
...args: Parameters<T>
10+
) => PromiseOrValue<void>;
11+
get: (
12+
...args: Parameters<T>
13+
) => PromiseOrValue<ReturnType<T> | Error | undefined>;
914
}
1015

1116
export function withCache<
12-
T extends (...args: Array<any>) => Exclude<any, undefined>,
17+
T extends (...args: Array<any>) => Exclude<any, Error | undefined>,
1318
>(
1419
fn: T,
1520
cache: FnCache<T>,
@@ -27,23 +32,43 @@ export function withCache<
2732
}
2833

2934
function handleCacheResult<
30-
T extends (...args: Array<any>) => Exclude<any, undefined>,
35+
T extends (...args: Array<any>) => Exclude<any, Error | undefined>,
3136
>(
32-
cachedResult: Awaited<ReturnType<T>> | undefined,
37+
cachedResult: Awaited<ReturnType<T>> | Error | undefined,
3338
fn: T,
3439
cache: FnCache<T>,
3540
args: Parameters<T>,
3641
): Awaited<ReturnType<T>> {
3742
if (cachedResult !== undefined) {
43+
if (cachedResult instanceof Error) {
44+
throw cachedResult;
45+
}
3846
return cachedResult;
3947
}
4048

41-
const result = fn(...args);
49+
let result;
50+
try {
51+
result = fn(...args);
52+
} catch (error) {
53+
updateResult(error, cache, args);
54+
throw error;
55+
}
56+
57+
updateResult(result, cache, args);
58+
return result;
59+
}
60+
61+
function updateResult<
62+
T extends (...args: Array<any>) => Exclude<any, Error | undefined>,
63+
>(
64+
result: Awaited<ReturnType<T>> | Error,
65+
cache: FnCache<T>,
66+
args: Parameters<T>,
67+
): void {
4268
const setResult = cache.set(result, ...args);
4369
if (isPromise(setResult)) {
4470
setResult.catch(() => {
4571
/* c8 ignore next */
4672
});
4773
}
48-
return result;
4974
}

src/language/__tests__/parser-cache-test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('Parser Cache', () => {
4646
};
4747

4848
it('parses asynchronously using asynchronous cache', async () => {
49-
let cachedDocument: DocumentNode | undefined;
49+
let cachedDocument: DocumentNode | Error | undefined;
5050
let getAttempts = 0;
5151
let cacheHits = 0;
5252
const customCache: ParseCache = {
@@ -86,7 +86,7 @@ describe('Parser Cache', () => {
8686
});
8787

8888
it('parses synchronously using cache with sync getter and async setter', async () => {
89-
let cachedDocument: DocumentNode | undefined;
89+
let cachedDocument: DocumentNode | Error | undefined;
9090
let getAttempts = 0;
9191
let cacheHits = 0;
9292
const customCache: ParseCache = {
@@ -123,7 +123,7 @@ describe('Parser Cache', () => {
123123
});
124124

125125
it('parses asynchronously using cache with async getter and sync setter', async () => {
126-
let cachedDocument: DocumentNode | undefined;
126+
let cachedDocument: DocumentNode | Error | undefined;
127127
let getAttempts = 0;
128128
let cacheHits = 0;
129129
const customCache: ParseCache = {
@@ -161,7 +161,7 @@ describe('Parser Cache', () => {
161161
});
162162

163163
it('parseSync parses synchronously using synchronous cache', () => {
164-
let cachedDocument: DocumentNode | undefined;
164+
let cachedDocument: DocumentNode | Error | undefined;
165165
let getAttempts = 0;
166166
let cacheHits = 0;
167167
const customCache: ParseCache = {
@@ -195,7 +195,7 @@ describe('Parser Cache', () => {
195195
});
196196

197197
it('parseSync throws using asynchronous cache', () => {
198-
let cachedDocument: DocumentNode | undefined;
198+
let cachedDocument: DocumentNode | Error | undefined;
199199
const customCache: ParseCache = {
200200
set: async (resultedDocument) => {
201201
await resolveOnNextTick();
@@ -212,7 +212,7 @@ describe('Parser Cache', () => {
212212
});
213213

214214
it('parseSync parses synchronously using sync getter and async setter', async () => {
215-
let cachedDocument: DocumentNode | undefined;
215+
let cachedDocument: DocumentNode | Error | undefined;
216216
let getAttempts = 0;
217217
let cacheHits = 0;
218218
const customCache: ParseCache = {
@@ -249,7 +249,7 @@ describe('Parser Cache', () => {
249249
});
250250

251251
it('parseSync throws using asynchronous cache', () => {
252-
let cachedDocument: DocumentNode | undefined;
252+
let cachedDocument: DocumentNode | Error | undefined;
253253
const customCache: ParseCache = {
254254
set: async (resultedDocument) => {
255255
await resolveOnNextTick();

src/language/parser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,14 @@ export interface ParseOptions {
120120

121121
export interface ParseCache {
122122
set: (
123-
document: DocumentNode,
123+
document: DocumentNode | Error,
124124
source: string | Source,
125125
options?: ParseOptions | undefined,
126126
) => PromiseOrValue<void> | void;
127127
get: (
128128
source: string | Source,
129129
options?: ParseOptions | undefined,
130-
) => PromiseOrValue<DocumentNode | undefined>;
130+
) => PromiseOrValue<DocumentNode | Error | undefined>;
131131
}
132132

133133
/**

src/validation/__tests__/validation-test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('Validate: Supports full validation', () => {
6363
}
6464
`);
6565

66-
let cachedErrors: ReadonlyArray<GraphQLError> | undefined;
66+
let cachedErrors: ReadonlyArray<GraphQLError> | Error | undefined;
6767
let getAttempts = 0;
6868
let cacheHits = 0;
6969
const customCache: ValidateCache = {
@@ -119,7 +119,7 @@ describe('Validate: Supports full validation', () => {
119119
}
120120
`);
121121

122-
let cachedErrors: ReadonlyArray<GraphQLError> | undefined;
122+
let cachedErrors: ReadonlyArray<GraphQLError> | Error | undefined;
123123
let getAttempts = 0;
124124
let cacheHits = 0;
125125
const customCache: ValidateCache = {
@@ -172,7 +172,7 @@ describe('Validate: Supports full validation', () => {
172172
}
173173
`);
174174

175-
let cachedErrors: ReadonlyArray<GraphQLError> | undefined;
175+
let cachedErrors: ReadonlyArray<GraphQLError> | Error | undefined;
176176
let getAttempts = 0;
177177
let cacheHits = 0;
178178
const customCache: ValidateCache = {
@@ -226,7 +226,7 @@ describe('Validate: Supports full validation', () => {
226226
}
227227
`);
228228

229-
let cachedErrors: ReadonlyArray<GraphQLError> | undefined;
229+
let cachedErrors: ReadonlyArray<GraphQLError> | Error | undefined;
230230
let getAttempts = 0;
231231
let cacheHits = 0;
232232
const customCache: ValidateCache = {
@@ -276,7 +276,7 @@ describe('Validate: Supports full validation', () => {
276276
}
277277
`);
278278

279-
let cachedErrors: ReadonlyArray<GraphQLError> | undefined;
279+
let cachedErrors: ReadonlyArray<GraphQLError> | Error | undefined;
280280
let getAttempts = 0;
281281
let cacheHits = 0;
282282
const customCache: ValidateCache = {
@@ -329,7 +329,7 @@ describe('Validate: Supports full validation', () => {
329329
}
330330
`);
331331

332-
let cachedErrors: ReadonlyArray<GraphQLError> | undefined;
332+
let cachedErrors: ReadonlyArray<GraphQLError> | Error | undefined;
333333
const customCache: ValidateCache = {
334334
set: async (resultedErrors) => {
335335
await resolveOnNextTick();

src/validation/validate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface ValidateOptions {
2828

2929
export interface ValidateCache {
3030
set: (
31-
errors: ReadonlyArray<GraphQLError>,
31+
errors: ReadonlyArray<GraphQLError> | Error,
3232
schema: GraphQLSchema,
3333
documentAST: DocumentNode,
3434
rules?: ReadonlyArray<ValidationRule> | undefined,
@@ -39,7 +39,7 @@ export interface ValidateCache {
3939
documentAST: DocumentNode,
4040
rules?: ReadonlyArray<ValidationRule> | undefined,
4141
options?: ValidateOptions | undefined,
42-
) => PromiseOrValue<ReadonlyArray<GraphQLError> | undefined>;
42+
) => PromiseOrValue<ReadonlyArray<GraphQLError> | Error | undefined>;
4343
}
4444

4545
/**

0 commit comments

Comments
 (0)