Skip to content

Commit 45edaed

Browse files
authored
chore(property-provider): refactor memoize to use arrow functions (#1281)
1 parent 6f5ddfc commit 45edaed

File tree

3 files changed

+151
-105
lines changed

3 files changed

+151
-105
lines changed

packages/credential-provider-node/src/index.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ describe("defaultProvider", () => {
371371

372372
expect(await provider()).toEqual(creds);
373373

374-
expect(provider()).toBe(provider());
374+
expect(provider()).toStrictEqual(provider());
375375

376376
expect(await provider()).toEqual(creds);
377377
expect((fromEnv() as any).mock.calls.length).toBe(1);
+100-53
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,126 @@
11
import { memoize } from "./memoize";
2-
import { Provider } from "@aws-sdk/types";
32

43
describe("memoize", () => {
4+
let provider: jest.Mock;
5+
const mockReturn = "foo";
6+
const repeatTimes = 10;
7+
8+
beforeEach(() => {
9+
provider = jest.fn().mockResolvedValue(mockReturn);
10+
});
11+
12+
afterEach(() => {
13+
jest.clearAllMocks();
14+
});
15+
516
describe("static memoization", () => {
617
it("should cache the resolved provider", async () => {
7-
const provider = jest.fn().mockResolvedValue("foo");
18+
expect.assertions(repeatTimes * 2);
19+
820
const memoized = memoize(provider);
921

10-
expect(await memoized()).toEqual("foo");
11-
expect(provider.mock.calls.length).toBe(1);
12-
expect(await memoized()).toEqual("foo");
13-
expect(provider.mock.calls.length).toBe(1);
22+
for (const index in [...Array(repeatTimes).keys()]) {
23+
expect(await memoized()).toStrictEqual(mockReturn);
24+
expect(provider).toHaveBeenCalledTimes(1);
25+
}
1426
});
1527

1628
it("should always return the same promise", () => {
17-
const provider = jest.fn().mockResolvedValue("foo");
29+
expect.assertions(repeatTimes * 2);
30+
1831
const memoized = memoize(provider);
1932
const result = memoized();
2033

21-
expect(memoized()).toBe(result);
34+
for (const index in [...Array(repeatTimes).keys()]) {
35+
expect(memoized()).toStrictEqual(result);
36+
expect(provider).toHaveBeenCalledTimes(1);
37+
}
2238
});
2339
});
2440

2541
describe("refreshing memoization", () => {
26-
it("should not reinvoke the underlying provider while isExpired returns `false`", async () => {
27-
const provider = jest.fn().mockResolvedValue("foo");
28-
const isExpired = jest.fn().mockReturnValue(false);
29-
const memoized = memoize(provider, isExpired);
30-
31-
const checkCount = 10;
32-
for (let i = 0; i < checkCount; i++) {
33-
expect(await memoized()).toBe("foo");
34-
}
42+
let isExpired: jest.Mock;
43+
let requiresRefresh: jest.Mock;
3544

36-
expect(isExpired.mock.calls.length).toBe(checkCount);
37-
expect(provider.mock.calls.length).toBe(1);
45+
beforeEach(() => {
46+
isExpired = jest.fn().mockReturnValue(true);
47+
requiresRefresh = jest.fn().mockReturnValue(false);
3848
});
3949

40-
it("should reinvoke the underlying provider when isExpired returns `true`", async () => {
41-
const provider = jest.fn().mockResolvedValue("foo");
42-
const isExpired = jest.fn().mockReturnValue(false);
43-
const memoized = memoize(provider, isExpired);
44-
45-
const checkCount = 10;
46-
for (let i = 0; i < checkCount; i++) {
47-
expect(await memoized()).toBe("foo");
48-
}
49-
50-
expect(isExpired.mock.calls.length).toBe(checkCount);
51-
expect(provider.mock.calls.length).toBe(1);
52-
53-
isExpired.mockReturnValueOnce(true);
54-
for (let i = 0; i < checkCount; i++) {
55-
expect(await memoized()).toBe("foo");
56-
}
57-
58-
expect(isExpired.mock.calls.length).toBe(checkCount * 2);
59-
expect(provider.mock.calls.length).toBe(2);
50+
describe("should not reinvoke the underlying provider while isExpired returns `false`", () => {
51+
const isExpiredFalseTest = async (requiresRefresh?: any) => {
52+
isExpired.mockReturnValue(false);
53+
const memoized = memoize(provider, isExpired, requiresRefresh);
54+
55+
for (const index in [...Array(repeatTimes).keys()]) {
56+
expect(await memoized()).toEqual(mockReturn);
57+
}
58+
59+
expect(isExpired).toHaveBeenCalledTimes(repeatTimes);
60+
if (requiresRefresh) {
61+
expect(requiresRefresh).toHaveBeenCalledTimes(repeatTimes);
62+
}
63+
expect(provider).toHaveBeenCalledTimes(1);
64+
};
65+
66+
it("when requiresRefresh is not passed", async () => {
67+
return isExpiredFalseTest();
68+
});
69+
70+
it("when requiresRefresh returns true", () => {
71+
requiresRefresh.mockReturnValue(true);
72+
return isExpiredFalseTest(requiresRefresh);
73+
});
6074
});
6175

62-
it("should return the same promise for invocations 2-infinity if `requiresRefresh` returns `false`", async () => {
63-
const provider = jest.fn().mockResolvedValue("foo");
64-
const isExpired = jest.fn().mockReturnValue(true);
65-
const requiresRefresh = jest.fn().mockReturnValue(false);
66-
67-
const memoized = memoize(provider, isExpired, requiresRefresh);
68-
expect(await memoized()).toBe("foo");
69-
const set = new Set<Promise<string>>();
70-
71-
const checkCount = 10;
72-
for (let i = 0; i < checkCount; i++) {
73-
set.add(memoized());
74-
}
76+
describe("should reinvoke the underlying provider when isExpired returns `true`", () => {
77+
const isExpiredTrueTest = async (requiresRefresh?: any) => {
78+
const memoized = memoize(provider, isExpired, requiresRefresh);
79+
80+
for (const index in [...Array(repeatTimes).keys()]) {
81+
expect(await memoized()).toEqual(mockReturn);
82+
}
83+
84+
expect(isExpired).toHaveBeenCalledTimes(repeatTimes);
85+
if (requiresRefresh) {
86+
expect(requiresRefresh).toHaveBeenCalledTimes(repeatTimes);
87+
}
88+
expect(provider).toHaveBeenCalledTimes(repeatTimes + 1);
89+
};
90+
91+
it("when requiresRefresh is not passed", () => {
92+
return isExpiredTrueTest();
93+
});
94+
95+
it("when requiresRefresh returns true", () => {
96+
requiresRefresh.mockReturnValue(true);
97+
return isExpiredTrueTest(requiresRefresh);
98+
});
99+
});
75100

76-
expect(set.size).toBe(1);
101+
describe("should return the same promise for invocations 2-infinity if `requiresRefresh` returns `false`", () => {
102+
const requiresRefreshFalseTest = async () => {
103+
const memoized = memoize(provider, isExpired, requiresRefresh);
104+
const result = memoized();
105+
expect(await result).toBe(mockReturn);
106+
107+
for (const index in [...Array(repeatTimes).keys()]) {
108+
expect(memoized()).toStrictEqual(result);
109+
expect(provider).toHaveBeenCalledTimes(1);
110+
}
111+
112+
expect(requiresRefresh).toHaveBeenCalledTimes(1);
113+
expect(isExpired).not.toHaveBeenCalled();
114+
};
115+
116+
it("when isExpired returns true", () => {
117+
return requiresRefreshFalseTest();
118+
});
119+
120+
it("when isExpired returns false", () => {
121+
isExpired.mockReturnValue(false);
122+
return requiresRefreshFalseTest();
123+
});
77124
});
78125
});
79126
});
+50-51
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,50 @@
11
import { Provider } from "@aws-sdk/types";
22

3-
/**
4-
*
5-
* Decorates a provider function with either static memoization.
6-
*
7-
* To create a statically memoized provider, supply a provider as the only
8-
* argument to this function. The provider will be invoked once, and all
9-
* invocations of the provider returned by `memoize` will return the same
10-
* promise object.
11-
*
12-
* @param provider The provider whose result should be cached indefinitely.
13-
*/
14-
export function memoize<T>(provider: Provider<T>): Provider<T>;
3+
interface MemoizeOverload {
4+
/**
5+
*
6+
* Decorates a provider function with either static memoization.
7+
*
8+
* To create a statically memoized provider, supply a provider as the only
9+
* argument to this function. The provider will be invoked once, and all
10+
* invocations of the provider returned by `memoize` will return the same
11+
* promise object.
12+
*
13+
* @param provider The provider whose result should be cached indefinitely.
14+
*/
15+
<T>(provider: Provider<T>): Provider<T>;
1516

16-
/**
17-
* Decorates a provider function with refreshing memoization.
18-
*
19-
* @param provider The provider whose result should be cached.
20-
* @param isExpired A function that will evaluate the resolved value and
21-
* determine if it is expired. For example, when
22-
* memoizing AWS credential providers, this function
23-
* should return `true` when the credential's
24-
* expiration is in the past (or very near future) and
25-
* `false` otherwise.
26-
* @param requiresRefresh A function that will evaluate the resolved value and
27-
* determine if it represents static value or one that
28-
* will eventually need to be refreshed. For example,
29-
* AWS credentials that have no defined expiration will
30-
* never need to be refreshed, so this function would
31-
* return `true` if the credentials resolved by the
32-
* underlying provider had an expiration and `false`
33-
* otherwise.
34-
*/
35-
export function memoize<T>(
36-
provider: Provider<T>,
37-
isExpired: (resolved: T) => boolean,
38-
requiresRefresh?: (resolved: T) => boolean
39-
): Provider<T>;
17+
/**
18+
* Decorates a provider function with refreshing memoization.
19+
*
20+
* @param provider The provider whose result should be cached.
21+
* @param isExpired A function that will evaluate the resolved value and
22+
* determine if it is expired. For example, when
23+
* memoizing AWS credential providers, this function
24+
* should return `true` when the credential's
25+
* expiration is in the past (or very near future) and
26+
* `false` otherwise.
27+
* @param requiresRefresh A function that will evaluate the resolved value and
28+
* determine if it represents static value or one that
29+
* will eventually need to be refreshed. For example,
30+
* AWS credentials that have no defined expiration will
31+
* never need to be refreshed, so this function would
32+
* return `true` if the credentials resolved by the
33+
* underlying provider had an expiration and `false`
34+
* otherwise.
35+
*/
36+
<T>(
37+
provider: Provider<T>,
38+
isExpired: (resolved: T) => boolean,
39+
requiresRefresh?: (resolved: T) => boolean
40+
): Provider<T>;
41+
}
4042

41-
export function memoize<T>(
43+
export const memoize: MemoizeOverload = <T>(
4244
provider: Provider<T>,
4345
isExpired?: (resolved: T) => boolean,
4446
requiresRefresh?: (resolved: T) => boolean
45-
): Provider<T> {
47+
): Provider<T> => {
4648
if (isExpired === undefined) {
4749
// This is a static memoization; no need to incorporate refreshing
4850
const result = provider();
@@ -52,22 +54,19 @@ export function memoize<T>(
5254
let result = provider();
5355
let isConstant: boolean = false;
5456

55-
return () => {
57+
return async () => {
5658
if (isConstant) {
5759
return result;
5860
}
5961

60-
return result.then(resolved => {
61-
if (requiresRefresh && !requiresRefresh(resolved)) {
62-
isConstant = true;
63-
return resolved;
64-
}
65-
66-
if (isExpired(resolved)) {
67-
return (result = provider());
68-
}
69-
62+
const resolved = await result;
63+
if (requiresRefresh && !requiresRefresh(resolved)) {
64+
isConstant = true;
7065
return resolved;
71-
});
66+
}
67+
if (isExpired(resolved)) {
68+
return (result = provider());
69+
}
70+
return resolved;
7271
};
73-
}
72+
};

0 commit comments

Comments
 (0)