Skip to content

Commit 627e890

Browse files
committed
Commit
1 parent 0017670 commit 627e890

File tree

1 file changed

+35
-64
lines changed

1 file changed

+35
-64
lines changed

README.md

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,19 @@ While it is much more powerful and can restore arbitrary complex structures, it
160160
It's best to explain by example. Assuming the data structure from above, we have:
161161

162162
```js
163-
const ctrl = new JSONParseNexus();
164-
const asyncData = {
165-
type: ctrl.eager('$.type'),
166-
items: ctrl.stream('$.items.*'),
163+
const parser = new JSONParseNexus();
164+
const data = {
165+
type: parser.promise('$.type'),
166+
items: parser.stream('$.items.*'),
167167
}
168168
(await fetch('/nested.json').body)
169-
.pipeThrough(ctrl) // <-- new
169+
.pipeThrough(parser) // <-- new
170170

171-
assertEquals(await asyncData.type, 'foo')
171+
assertEquals(await data.type, 'foo')
172172

173173
// We can collect the values as before:
174174
const collected = [];
175-
await asyncData.items
175+
await data.items
176176
.pipeTo(new WritableStream({ write(obj) { collected.push(obj) }}))
177177
```
178178

@@ -187,88 +187,59 @@ This means that memory usage can grow arbitrarily large unless the data is proce
187187
Take for example the following structure:
188188

189189
```js
190-
const ctrl = new JSONParseNexus();
190+
const parser = new JSONParseNexus();
191191

192192
jsonStringifyStream({
193193
xs: new Array(10_000).fill({ x: 'x' }),
194194
ys: new Array(10_000).fill({ y: 'y' }),
195-
}).pipeThrough(ctrl)
195+
}).pipeThrough(parser)
196196

197-
for await (const y of ctrl.iterable('$.ys.*')) console.log(y)
198-
for await (const x of ctrl.iterable('$.xs.*')) console.log(x)
197+
for await (const y of parser.iterable('$.ys.*')) console.log(y)
198+
for await (const x of parser.iterable('$.xs.*')) console.log(x)
199199
```
200200

201201
In this examples Ys are being processed before Xs, but were stringified in the opposite order.
202202
This means the internal queue of Xs grows to 10.000 before it is being processed by the second loop.
203203
This can be avoided by changing the order to match the stringification order.
204204

205-
### Eager and Lazy Promises
206-
Special attention has to be given single values, as Promises in JS have no concept of "pulling" data.
207-
`JSONParseNexus` provides two separate methods to request a single value: `.eager` and `.lazy`.
208-
Both return promises that resolve with the requested value, but they differ in their effect on the internal stream:
209-
The former starts pulling values from the stream immediately until the requested value is found,
210-
while the later will only resolve if another consumer advances the parser's cursor beyond the point where the requested value is located.
211-
212-
Both approaches have their pitfalls.
213-
Requesting a value eager might parse an arbitrary amount of JSON, fill up queues and remove other's consumers ability to control the pace of data.
214-
Requesting values lazily on the other hand might block execution entirely.
215-
216-
TODO: Find a better solution. Perhaps pull as part of `.then` call??
205+
### Single Values and Lazy Promises
206+
Special attention has to be given single values, as Promises in JS are eager by default and have no concept of "pulling" data.
207+
`JSONParseNexus` introduces a lazy promise type that has a different behavior.
208+
As with async iterables and streams provided by `.iterable` and `.stream`, it does not pull values form the underlying readable until requested. This happens when `await`ing the promise, i.e. is calling the `.then` instance method, otherwise it stays idle.
217209

218210
```js
219-
const ctrl = new JSONParseNexus();
211+
const parser = new JSONParseNexus();
220212

221213
jsonStringifyStream({
222214
type: 'items',
223215
items: new Array(10_000).fill({ x: 'x' }),
224216
trailer: 'trail',
225-
}).pipeThrough(ctrl)
217+
}).pipeThrough(parser)
226218

227219
const data = {
228-
type: ctrl.eager('$.type') // Fine
229-
items: ctrl.iterable('$.items.*') // Fine
230-
trailer: ctrl.lazy('$.trailer')
220+
type: await parser.promise('$.type') // ok
221+
items: parser.iterable('$.items.*')
222+
trailer: parser.promise('$.trailer') // do not await!
231223
}
232224

233-
const type = await data.type
234-
for await (const x of data.items) console.log(x)
235-
const trailer = await data.trailer.pull()
236-
```
237-
238-
In this example the use of `.eager` has unintended consequences. TBC
239-
240-
<!-- ```js
241-
const ctrl = new JSONParseNexus();
225+
console.log(data.type) //=> 'items'
242226

243-
const data = {
244-
type: ctrl.lazy('$.type') // Fine
245-
items: ctrl.iterable('$.items.*') // Fine
246-
trailer: ctrl.lazy('$.trailer') // Oh-Oh
227+
// Now async iteration is in control of parser:
228+
for await (const x of data.items) {
229+
console.log(x)
247230
}
231+
// Now we can await the trailer:
232+
console.log(await data.trailer)
233+
```
248234

249-
jsonStringifyStream({
250-
type: 'items',
251-
items: new Array(10_000).fill({ x: 'x' }),
252-
trailer: 'trail',
253-
}).pipeThrough(ctrl)
254-
255-
const type = await data.type.pull()
256-
for await (const x of data.items) console.log(x)
257-
const trailer = await data.trailer.pull()
258-
``` -->
259-
260-
261-
<!-- Note that there are many pitfalls with this feature.
262-
~~Internally, `.stream` and `.iterable` tee the object stream and filter for the requested JSON paths.~~
263-
Internally `JSONParseNexus` manages multiple queues that it fills i
264-
This means memory usage can grow arbitrary large if the values aren't consumed in the same order as they arrive
265-
(TODO: actually, the queue grows large the main .readable isn't consumed. Could fix with some trickery. Maybe last call to `stream` doesn't tee the value?) -->
266-
267-
<!-- ~~Note that `.promise` by itself does not pull values from the stream. If it isn't combined with `pipeTo` or similar, it will never resolve.~~
268-
~~If it is awaited before sufficient values have been pulled form the stream it will never resolve!~~ -->
235+
In the above example, without lazy promises `ctrl.promise('$.trailer')` would immediately parse the entire JSON structure, which involves filling a queue of 10.000 elements.
269236

270-
<!-- Note that the promise might resolve with `undefined` if the corresponding JSON path is not found in the stream. -->
237+
In order to transform value without triggering executions,
238+
the class provides a `.map` function that works similar to JS arrays:
271239

240+
```js
241+
const trailer = ctrl.promise('$.trailer').map(x => x.toUpperCase())
242+
```
272243

273244
## Limitations
274245
**JSON Stream** largely consists of old Node libraries that have been modified to work in Worker Environments and the browser.
@@ -290,11 +261,11 @@ function toReadableStream<T>(iter: Iterable<T>) {
290261
}
291262
```
292263

293-
## Deno: Stream from Filesystem
264+
<!-- ## Deno: Stream from Filesystem
294265
When reading JSON from a file system, nothing special is required:
295266
296267
```js
297268
new Response((await Deno.open('./nested.json')).readable, {
298269
headers: [['content-type', 'application/json']]
299270
})
300-
```
271+
``` -->

0 commit comments

Comments
 (0)