Skip to content

Commit 6955b68

Browse files
authored
avoid printing full error during development (#59)
* avoid printing error in development * add description * add changeset * change message * add more tests
1 parent 2388982 commit 6955b68

File tree

3 files changed

+84
-55
lines changed

3 files changed

+84
-55
lines changed

.changeset/fair-candles-visit.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@vercel/flags': patch
3+
---
4+
5+
emit reduced console info when falling back to defaultValue during development

packages/flags/src/next/index.test.ts

+39-5
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ describe('flag on app router', () => {
190190
expect(mockDecide).toHaveBeenCalledTimes(1);
191191
});
192192

193+
it('falls back to the defaultValue if a sync decide throws', async () => {
194+
const mockDecide = vi.fn(() => {
195+
throw new Error('custom error');
196+
});
197+
198+
const f = flag<boolean>({
199+
key: 'first-flag',
200+
decide: mockDecide,
201+
defaultValue: false,
202+
});
203+
204+
mocks.headers.mockReturnValueOnce(new Headers());
205+
206+
await expect(f()).resolves.toEqual(false);
207+
expect(mockDecide).toHaveBeenCalledTimes(1);
208+
});
209+
193210
it('falls back to the defaultValue when a decide function returns undefined', async () => {
194211
const syncFlag = flag<boolean>({
195212
key: 'sync-flag',
@@ -249,12 +266,10 @@ describe('flag on pages router', () => {
249266

250267
expect(f).toHaveProperty('key', 'first-flag');
251268

252-
const fakeRequest = {
253-
headers: {},
254-
cookies: {},
255-
} as unknown as IncomingMessage & { cookies: NextApiRequestCookies };
269+
const [firstRequest, socket1] = createRequest();
256270

257-
await expect(f(fakeRequest)).resolves.toEqual(false);
271+
await expect(f(firstRequest)).resolves.toEqual(false);
272+
socket1.destroy();
258273
});
259274

260275
it('caches for the duration of a request', async () => {
@@ -433,6 +448,25 @@ describe('flag on pages router', () => {
433448
expect(catchFn).not.toHaveBeenCalled();
434449
expect(mockDecide).toHaveBeenCalledTimes(1);
435450
});
451+
452+
it('falls back to the defaultValue if a sync decide throws', async () => {
453+
const mockDecide = vi.fn(() => {
454+
throw new Error('custom error');
455+
});
456+
457+
const [firstRequest, socket1] = createRequest();
458+
const f = flag<boolean>({
459+
key: 'first-flag',
460+
decide: mockDecide,
461+
defaultValue: false,
462+
});
463+
464+
mocks.headers.mockReturnValueOnce(new Headers());
465+
466+
await expect(f(firstRequest)).resolves.toEqual(false);
467+
expect(mockDecide).toHaveBeenCalledTimes(1);
468+
socket1.destroy();
469+
});
436470
});
437471

438472
describe('dynamic io', () => {

packages/flags/src/next/index.ts

+40-50
Original file line numberDiff line numberDiff line change
@@ -279,60 +279,50 @@ function getRun<ValueType, EntitiesType>(
279279
return decision;
280280
}
281281

282-
// fall back to defaultValue if it is set,
283-
let decisionPromise: Promise<ValueType> | ValueType;
284-
try {
285-
decisionPromise = Promise.resolve<ValueType>(
286-
decide({
287-
headers: readonlyHeaders,
288-
cookies: readonlyCookies,
289-
entities,
290-
}),
291-
)
292-
// catch errors in async "decide" functions
293-
.then<ValueType, ValueType>(
294-
(value) => {
295-
if (value !== undefined) return value;
296-
if (definition.defaultValue !== undefined)
297-
return definition.defaultValue;
298-
throw new Error(
299-
`@vercel/flags: Flag "${definition.key}" must have a defaultValue or a decide function that returns a value`,
300-
);
301-
},
302-
(error: Error) => {
303-
if (isInternalNextError(error)) throw error;
304-
305-
// try to recover if defaultValue is set
306-
if (definition.defaultValue !== undefined) {
282+
// We use an async iife to ensure we can catch both sync and async errors of
283+
// the original decide function, as that one is not guaranted to be async.
284+
//
285+
// Also fall back to defaultValue when the decide function returns undefined or throws an error.
286+
const decisionPromise = (async () => {
287+
return decide({
288+
headers: readonlyHeaders,
289+
cookies: readonlyCookies,
290+
entities,
291+
});
292+
})()
293+
// catch errors in async "decide" functions
294+
.then<ValueType, ValueType>(
295+
(value) => {
296+
if (value !== undefined) return value;
297+
if (definition.defaultValue !== undefined)
298+
return definition.defaultValue;
299+
throw new Error(
300+
`@vercel/flags: Flag "${definition.key}" must have a defaultValue or a decide function that returns a value`,
301+
);
302+
},
303+
(error: Error) => {
304+
if (isInternalNextError(error)) throw error;
305+
306+
// try to recover if defaultValue is set
307+
if (definition.defaultValue !== undefined) {
308+
if (process.env.NODE_ENV === 'development') {
309+
console.info(
310+
`@vercel/flags: Flag "${definition.key}" is falling back to its defaultValue`,
311+
);
312+
} else {
307313
console.warn(
308-
`@vercel/flags: Flag "${definition.key}" is falling back to the defaultValue after catching the following error`,
314+
`@vercel/flags: Flag "${definition.key}" is falling back to its defaultValue after catching the following error`,
309315
error,
310316
);
311-
return definition.defaultValue;
312317
}
313-
console.warn(
314-
`@vercel/flags: Flag "${definition.key}" could not be evaluated`,
315-
);
316-
throw error;
317-
},
318-
);
319-
} catch (error) {
320-
if (isInternalNextError(error)) throw error;
321-
322-
// catch errors in sync "decide" functions
323-
if (definition.defaultValue !== undefined) {
324-
console.warn(
325-
`@vercel/flags: Flag "${definition.key}" is falling back to the defaultValue after catching the following error`,
326-
error,
327-
);
328-
decisionPromise = Promise.resolve(definition.defaultValue);
329-
} else {
330-
console.warn(
331-
`@vercel/flags: Flag "${definition.key}" could not be evaluated`,
332-
);
333-
throw error;
334-
}
335-
}
318+
return definition.defaultValue;
319+
}
320+
console.warn(
321+
`@vercel/flags: Flag "${definition.key}" could not be evaluated`,
322+
);
323+
throw error;
324+
},
325+
);
336326

337327
setCachedValuePromise(
338328
readonlyHeaders,

0 commit comments

Comments
 (0)