Skip to content

Commit 7b8954d

Browse files
authored
chore(shared-ini-file-loader): use Promises in slurpFile hash (#3546)
1 parent 906775f commit 7b8954d

File tree

2 files changed

+47
-60
lines changed

2 files changed

+47
-60
lines changed

packages/shared-ini-file-loader/src/slurpFile.spec.ts

+40-19
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,56 @@ describe("slurpFile", () => {
88
const getMockFileContents = (path: string, options = UTF8) => JSON.stringify({ path, options });
99

1010
beforeEach(() => {
11-
(promises.readFile as jest.Mock).mockImplementation((path, options) =>
12-
Promise.resolve(getMockFileContents(path, options))
13-
);
11+
(promises.readFile as jest.Mock).mockImplementation(async (path, options) => {
12+
await new Promise((resolve) => setTimeout(resolve, 100));
13+
return getMockFileContents(path, options);
14+
});
1415
});
1516

1617
afterEach(() => {
1718
jest.clearAllMocks();
1819
});
1920

20-
it("makes one readFile call for a filepath irrepsective of slurpFile calls", (done) => {
21-
jest.isolateModules(async () => {
22-
const { slurpFile } = require("./slurpFile");
23-
const mockPath = "/mock/path";
24-
const mockPathContent = getMockFileContents(mockPath);
21+
describe("makes one readFile call for a filepath irrespective of slurpFile calls", () => {
22+
// @ts-ignore: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34617
23+
it.each([10, 100, 1000, 10000])("parallel calls: %d ", (num: number, done: Function) => {
24+
jest.isolateModules(async () => {
25+
const { slurpFile } = require("./slurpFile");
26+
const mockPath = "/mock/path";
27+
const mockPathContent = getMockFileContents(mockPath);
28+
29+
expect(promises.readFile).not.toHaveBeenCalled();
30+
const fileContentArr = await Promise.all(Array(num).fill(slurpFile(mockPath)));
31+
expect(fileContentArr).toStrictEqual(Array(num).fill(mockPathContent));
32+
33+
// There is one readFile call even through slurpFile is called in parallel num times.
34+
expect(promises.readFile).toHaveBeenCalledTimes(1);
35+
expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8);
36+
done();
37+
});
38+
});
2539

26-
expect(promises.readFile).not.toHaveBeenCalled();
27-
const fileContentArr = await Promise.all([slurpFile(mockPath), slurpFile(mockPath)]);
28-
expect(fileContentArr).toStrictEqual([mockPathContent, mockPathContent]);
40+
it("two parallel calls and one sequential call", (done) => {
41+
jest.isolateModules(async () => {
42+
const { slurpFile } = require("./slurpFile");
43+
const mockPath = "/mock/path";
44+
const mockPathContent = getMockFileContents(mockPath);
2945

30-
// There is one readFile call even through slurpFile is called in parallel twice.
31-
expect(promises.readFile).toHaveBeenCalledTimes(1);
32-
expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8);
46+
expect(promises.readFile).not.toHaveBeenCalled();
47+
const fileContentArr = await Promise.all([slurpFile(mockPath), slurpFile(mockPath)]);
48+
expect(fileContentArr).toStrictEqual([mockPathContent, mockPathContent]);
3349

34-
const fileContent = await slurpFile(mockPath);
35-
expect(fileContent).toStrictEqual(mockPathContent);
50+
// There is one readFile call even through slurpFile is called in parallel twice.
51+
expect(promises.readFile).toHaveBeenCalledTimes(1);
52+
expect(promises.readFile).toHaveBeenCalledWith(mockPath, UTF8);
3653

37-
// There is one readFile call even through slurpFile is called for the third time.
38-
expect(promises.readFile).toHaveBeenCalledTimes(1);
39-
done();
54+
const fileContent = await slurpFile(mockPath);
55+
expect(fileContent).toStrictEqual(mockPathContent);
56+
57+
// There is one readFile call even through slurpFile is called for the third time.
58+
expect(promises.readFile).toHaveBeenCalledTimes(1);
59+
done();
60+
});
4061
});
4162
});
4263

packages/shared-ini-file-loader/src/slurpFile.ts

+7-41
Original file line numberDiff line numberDiff line change
@@ -3,45 +3,11 @@ import { promises as fsPromises } from "fs";
33

44
const { readFile } = fsPromises;
55

6-
type callbacks = { resolve: Function; reject: Function };
7-
type FileStatus = {
8-
contents: string;
9-
isReading: boolean;
10-
requestQueue: callbacks[];
11-
};
12-
13-
const fileStatusHash: { [key: string]: FileStatus } = {};
6+
const filePromisesHash: { [key: string]: Promise<string> } = {};
147

15-
export const slurpFile = (path: string) =>
16-
new Promise<string>((resolve, reject) => {
17-
if (!fileStatusHash[path]) {
18-
// File not read yet, set file isReading to true and read file.
19-
fileStatusHash[path] = { isReading: true, contents: "", requestQueue: [] };
20-
fileStatusHash[path].requestQueue.push({ resolve, reject });
21-
readFile(path, "utf8")
22-
.then((data) => {
23-
// File read successful
24-
fileStatusHash[path].isReading = false;
25-
fileStatusHash[path].contents = data;
26-
const { requestQueue } = fileStatusHash[path];
27-
while (requestQueue.length) {
28-
const { resolve } = requestQueue.pop()!;
29-
resolve(data);
30-
}
31-
})
32-
.catch((err) => {
33-
// File read failed;
34-
fileStatusHash[path].isReading = false;
35-
const { requestQueue } = fileStatusHash[path];
36-
while (requestQueue.length) {
37-
const { reject } = requestQueue.pop()!;
38-
reject(err);
39-
}
40-
});
41-
} else if (fileStatusHash[path].isReading) {
42-
// File currently being read. Add callbacks to the request queue.
43-
fileStatusHash[path].requestQueue.push({ resolve, reject });
44-
} else {
45-
resolve(fileStatusHash[path].contents);
46-
}
47-
});
8+
export const slurpFile = (path: string) => {
9+
if (!filePromisesHash[path]) {
10+
filePromisesHash[path] = readFile(path, "utf8");
11+
}
12+
return filePromisesHash[path];
13+
};

0 commit comments

Comments
 (0)