-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest.html
93 lines (86 loc) · 3.41 KB
/
test.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Streaming JSON deserialisation test</title>
</head>
<body>
<div id="streaming"></div>
</body>
<script>
async function main() {
const root = document.getElementById('streaming');
const response = await fetch('/json');
for await (const item of slurpJson(response)) {
const para = document.createElement('p');
para.innerText = item.message;
root.appendChild(para);
}
}
main();
</script>
<script>
class BeforeArrayState {
processNext(char) {
if (char === '[') return { newState: new BetweenElementsState(), includeInBuffer: false, yieldBuffer: false };
else if (!' \n'.includes(char)) throw new Error(`Unexpected character ${char} before start of top-level array`);
else return { newState: this, includeInBuffer: false, yieldBuffer: false };
}
}
class BetweenElementsState {
processNext(char) {
if (', \n'.includes(char)) return { newState: this, includeInBuffer: false, yieldBuffer: false };
else if (char === ']') return { newState: undefined, includeInBuffer: false, yieldBuffer: false };
else return { newState: new InsideElementState(char), includeInBuffer: true, yieldBuffer: false };
}
}
class InsideElementState {
constructor(firstChar) {
this.nestingDepth = '{['.includes(firstChar) ? 1 : 0;
this.insideString = firstChar === '"';
this.isEscapeSequence = false;
}
processNext(char) {
if (this.insideString) {
if (char === '\\') {
this.isEscapeSequence = true;
} else if (this.isEscapeSequence) {
this.isEscapeSequence = false;
} else if (char === '"') {
this.insideString = false;
}
} else if (char === '"') {
this.insideString = true;
} else if ('{['.includes(char)) {
this.nestingDepth++;
} else if ('}]'.includes(char)) {
this.nestingDepth--;
if(this.nestingDepth < 0) return { newState: undefined, includeInBuffer: false, yieldBuffer: true };
} else if (this.nestingDepth === 0 && char === ',') {
return { newState: new BetweenElementsState(), includeInBuffer: false, yieldBuffer: true };
}
return { newState: this, includeInBuffer: this.insideString || !' \n'.includes(char), yieldBuffer: false };
}
}
async function* slurpJson(jsonResponse) {
const textStream = jsonResponse.body.pipeThrough(new TextDecoderStream()).getReader();
let state = new BeforeArrayState();
buffer = '';
while (true) {
const { value, done } = await textStream.read();
if (done) break;
for (const char of value) {
if (!state) break;
const { newState, includeInBuffer, yieldBuffer } = state.processNext(char);
if (includeInBuffer) buffer += char;
if (yieldBuffer) {
yield JSON.parse(buffer);
buffer = '';
}
state = newState;
}
}
}
</script>
</html>