-
Notifications
You must be signed in to change notification settings - Fork 0
/
withProgress.ts
94 lines (74 loc) · 3.25 KB
/
withProgress.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
namespace jo {
export function withProgress<TArgs extends any[]>({
onProgress,
getCancelToken,
}: {
onProgress: (info: {loaded: number; total?: number}) => void;
getCancelToken?: (args: TArgs) => CancelToken | undefined;
}) {
// https://github.com/AnthumChris/fetch-progress-indicators/
logger.log(`Added progress for fetch...`);
return <TFn extends typeof fetch>(fn: TFn): TFn => ((...args: TArgs) => {
const [input, init] = args;
const request = (input instanceof Request) ? input : new Request(input);
const cancelToken = getCancelToken ? getCancelToken(args) : undefined;
return fn(request, init).then(response => {
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
if (!response.body) throw Error('ReadableStream not yet supported in this browser.');
if (cancelToken) {
// this occurs if cancel() was called before server responded (before fetch() Promise resolved)
if (cancelToken.requested) {
response.body.getReader().cancel();
cancelToken.throwIfRequested();
}
}
// Server must send CORS header "Access-Control-Expose-Headers: content-length" to access
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Expose-Headers
const contentLength = response.headers.get('content-length');
if (contentLength === null) logger.error('Content-Length response header unavailable, no total available for progess.');
let loaded = 0;
let total = contentLength === null ? undefined : +contentLength;
onProgress({loaded, total});
const reader = response.body.getReader();
return new Response(
new (ReadableStream as any)({
start(controller: ReadableByteStreamController) {
function read() {
// Cancelled?
if (cancelToken && cancelToken.requested) {
logger.log('Cancelling fetch progress reader');
reader.cancel();
// TODO: CancelError not available
try {
cancelToken.throwIfRequested();
} catch (cancelError) {
controller.error(cancelError);
}
return;
}
reader.read()
.then(({done, value}) => {
if (done) {
// ensure onProgress called when content-length=0
if (total === 0) { onProgress({loaded, total}); }
controller.close();
return;
}
loaded += value.byteLength;
onProgress({loaded, total});
controller.enqueue(value);
read();
})
.catch(error => {
logger.error(`Fetch progress reading error ${error.name}: ${error.message}`);
controller.error(error);
});
}
read();
}
})
);
});
}) as any;
}
}