Skip to content

Commit 9bfcb4b

Browse files
authored
Introduce batched requests for async-loops (#78)
* feat: introduce batched requests for async-loops * Update README.md
1 parent 2364bad commit 9bfcb4b

File tree

3 files changed

+84
-47
lines changed

3 files changed

+84
-47
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,10 @@ In addition, you can set the `filesToDelete` property as an array of strings (fi
104104
"ignoreDeletionFailures": true,
105105
}
106106
```
107+
108+
- If `batchSize` is set, then file deletions and file uploads will use batched concurrent requests as opposed to iterating through them. This can be helpful for uploading many small files. Beware of your Github usage limits.
109+
110+
```javascript
111+
{
112+
"batchSize": 10
113+
}

create-or-update-files.js

+72-47
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ module.exports = function(octokit, opts) {
1313
return reject("No changes provided");
1414
}
1515

16+
if (!opts.batchSize) {
17+
opts.batchSize = 1;
18+
}
19+
20+
if (typeof opts.batchSize !== "number") {
21+
return reject(`batchSize must be a number`);
22+
}
23+
1624
// Destructuring for easier access later
1725
let {
1826
owner,
@@ -22,7 +30,8 @@ module.exports = function(octokit, opts) {
2230
createBranch,
2331
committer,
2432
author,
25-
changes
33+
changes,
34+
batchSize
2635
} = opts;
2736

2837
let branchAlreadyExists = true;
@@ -81,60 +90,68 @@ module.exports = function(octokit, opts) {
8190
const treeItems = [];
8291
// Handle file deletions
8392
if (hasFilesToDelete) {
84-
for (const fileName of change.filesToDelete) {
85-
const exists = await fileExistsInRepo(
86-
octokit,
87-
owner,
88-
repo,
89-
fileName,
90-
baseTree
93+
for (const batch of chunk(change.filesToDelete, batchSize)) {
94+
await Promise.all(
95+
batch.map(async fileName => {
96+
const exists = await fileExistsInRepo(
97+
octokit,
98+
owner,
99+
repo,
100+
fileName,
101+
baseTree
102+
);
103+
104+
// If it doesn't exist, and we're not ignoring missing files
105+
// reject the promise
106+
if (!exists && !change.ignoreDeletionFailures) {
107+
return reject(
108+
`The file ${fileName} could not be found in the repo`
109+
);
110+
}
111+
112+
// At this point it either exists, or we're ignoring failures
113+
if (exists) {
114+
treeItems.push({
115+
path: fileName,
116+
sha: null, // sha as null implies that the file should be deleted
117+
mode: "100644",
118+
type: "commit"
119+
});
120+
}
121+
})
91122
);
123+
}
124+
}
92125

93-
// If it doesn't exist, and we're not ignoring missing files
94-
// reject the promise
95-
if (!exists && !change.ignoreDeletionFailures) {
96-
return reject(
97-
`The file ${fileName} could not be found in the repo`
126+
for (const batch of chunk(Object.keys(change.files), batchSize)) {
127+
await Promise.all(
128+
batch.map(async fileName => {
129+
const properties = change.files[fileName] || "";
130+
131+
const contents = properties.contents || properties;
132+
const mode = properties.mode || "100644";
133+
const type = properties.type || "blob";
134+
135+
if (!contents) {
136+
return reject(`No file contents provided for ${fileName}`);
137+
}
138+
139+
const fileSha = await createBlob(
140+
octokit,
141+
owner,
142+
repo,
143+
contents,
144+
type
98145
);
99-
}
100146

101-
// At this point it either exists, or we're ignoring failures
102-
if (exists) {
103147
treeItems.push({
104148
path: fileName,
105-
sha: null, // sha as null implies that the file should be deleted
106-
mode: "100644",
107-
type: "commit"
149+
sha: fileSha,
150+
mode: mode,
151+
type: type
108152
});
109-
}
110-
}
111-
}
112-
113-
for (const fileName in change.files) {
114-
const properties = change.files[fileName] || "";
115-
116-
const contents = properties.contents || properties;
117-
const mode = properties.mode || "100644";
118-
const type = properties.type || "blob";
119-
120-
if (!contents) {
121-
return reject(`No file contents provided for ${fileName}`);
122-
}
123-
124-
const fileSha = await createBlob(
125-
octokit,
126-
owner,
127-
repo,
128-
contents,
129-
type
153+
})
130154
);
131-
132-
treeItems.push({
133-
path: fileName,
134-
sha: fileSha,
135-
mode: mode,
136-
type: type
137-
});
138155
}
139156

140157
// no need to issue further requests if there are no updates, creations and deletions
@@ -278,3 +295,11 @@ async function loadRef(octokit, owner, repo, ref) {
278295
// console.log(e);
279296
}
280297
}
298+
299+
const chunk = (input, size) => {
300+
return input.reduce((arr, item, idx) => {
301+
return idx % size === 0
302+
? [...arr, [item]]
303+
: [...arr.slice(0, -1), [...arr.slice(-1)[0], item]];
304+
}, []);
305+
};

create-or-update-files.test.js

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ test(`empty parameter (changes)`, () => {
6161
expect(run(body)).rejects.toEqual(`No changes provided`);
6262
});
6363

64+
test(`non-number batchSize (changes)`, () => {
65+
const body = { ...validRequest, batchSize: "5" };
66+
expect(run(body)).rejects.toEqual(`batchSize must be a number`);
67+
});
68+
6469
test(`branch does not exist, createBranch false`, async () => {
6570
mockGetRef(branch, `sha-${branch}`, false);
6671
const body = { ...validRequest, createBranch: false };

0 commit comments

Comments
 (0)