Skip to content

Commit fcbe2e8

Browse files
committed
Fix saferFetch retry behavior
1 parent c18d7df commit fcbe2e8

File tree

4 files changed

+36
-17
lines changed

4 files changed

+36
-17
lines changed

src/FetchTool.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const {scratchFetch} = require('./scratchFetch');
22
const saferFetch = require('./safer-fetch');
3+
const isNullResponse = require('./isNullResponse');
34

45
/**
56
* @typedef {Request & {withCredentials: boolean}} ScratchSendRequest
@@ -27,7 +28,7 @@ class FetchTool {
2728
return saferFetch(url, Object.assign({method: 'GET'}, options))
2829
.then(result => {
2930
if (result.ok) return result.arrayBuffer().then(b => new Uint8Array(b));
30-
if (result.status === 404) return null;
31+
if (isNullResponse(result)) return null;
3132
return Promise.reject(result.status); // TODO: we should throw a proper error
3233
});
3334
}

src/FetchWorkerTool.worker.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-env worker */
22

3+
const isNullResponse = require('./isNullResponse');
34
const saferFetch = require('./safer-fetch');
45

56
const complete = [];
@@ -37,7 +38,7 @@ const onMessage = ({data: job}) => {
3738
saferFetch(job.url, job.options)
3839
.then(result => {
3940
if (result.ok) return result.arrayBuffer();
40-
if (result.status === 404) return null;
41+
if (isNullResponse(result)) return null;
4142
return Promise.reject(result.status);
4243
})
4344
.then(buffer => complete.push({id: job.id, buffer}))

src/isNullResponse.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @param {Response} response the response from fetch()
3+
* @returns {boolean} true if the response is a "null response" where we successfully talked to the
4+
* source, but the source has no data for us.
5+
*/
6+
const isNullResponse = response => (
7+
// can't access, eg. due to expired/missing project token
8+
response.status === 403 ||
9+
10+
// assets does not exist
11+
// assets.scratch.mit.edu also returns 503 for missing assets
12+
response.status === 404 ||
13+
response.status === 503
14+
);
15+
16+
module.exports = isNullResponse;

src/safer-fetch.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@ const {scratchFetch} = require('./scratchFetch');
88
let currentFetches = 0;
99
const queue = [];
1010

11+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
12+
1113
const startNextFetch = ([resolve, url, options]) => {
1214
let firstError;
1315
let failedAttempts = 0;
1416

17+
const done = result => {
18+
currentFetches--;
19+
checkStartNextFetch();
20+
resolve(result);
21+
};
22+
1523
const attemptToFetch = () => scratchFetch(url, options)
16-
.then(result => {
17-
currentFetches--;
18-
checkStartNextFetch();
19-
return result;
20-
})
24+
.then(done)
2125
.catch(error => {
22-
if (error === 403) {
23-
// Retrying this request will not help, so return an error now.
24-
throw error;
25-
}
26+
// If fetch() errors, it means there was a network error of some sort.
27+
// This is worth retrying, especially as some browser will randomly fail requests
28+
// if we send too many at once (as we do).
2629

2730
console.warn(`Attempt to fetch ${url} failed`, error);
2831
if (!firstError) {
@@ -31,16 +34,14 @@ const startNextFetch = ([resolve, url, options]) => {
3134

3235
if (failedAttempts < 2) {
3336
failedAttempts++;
34-
return new Promise(cb => setTimeout(cb, (failedAttempts + Math.random() - 1) * 5000))
35-
.then(attemptToFetch);
37+
sleep((failedAttempts + Math.random() - 1) * 5000).then(attemptToFetch);
38+
return;
3639
}
3740

38-
currentFetches--;
39-
checkStartNextFetch();
40-
throw firstError;
41+
done(Promise.reject(firstError));
4142
});
4243

43-
return resolve(attemptToFetch());
44+
attemptToFetch();
4445
};
4546

4647
const checkStartNextFetch = () => {

0 commit comments

Comments
 (0)