2023-01-03 10:47:44 +00:00
|
|
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
2023-03-18 12:36:00 +00:00
|
|
|
// This module is browser compatible.
|
2022-11-29 13:55:38 +00:00
|
|
|
|
|
|
|
/**
|
2023-12-04 06:12:52 +00:00
|
|
|
* Convert the generator function into a {@linkcode TransformStream}.
|
2022-11-29 13:55:38 +00:00
|
|
|
*
|
2023-12-04 06:12:52 +00:00
|
|
|
* @example
|
2022-11-29 13:55:38 +00:00
|
|
|
* ```ts
|
|
|
|
* import { toTransformStream } from "https://deno.land/std@$STD_VERSION/streams/to_transform_stream.ts";
|
|
|
|
*
|
2023-07-17 06:08:20 +00:00
|
|
|
* const readable = ReadableStream.from([0, 1, 2])
|
2022-11-29 13:55:38 +00:00
|
|
|
* .pipeThrough(toTransformStream(async function* (src) {
|
|
|
|
* for await (const chunk of src) {
|
|
|
|
* yield chunk * 100;
|
|
|
|
* }
|
|
|
|
* }));
|
|
|
|
*
|
|
|
|
* for await (const chunk of readable) {
|
|
|
|
* console.log(chunk);
|
|
|
|
* }
|
|
|
|
* // output: 0, 100, 200
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* @param transformer A function to transform.
|
|
|
|
* @param writableStrategy An object that optionally defines a queuing strategy for the stream.
|
|
|
|
* @param readableStrategy An object that optionally defines a queuing strategy for the stream.
|
|
|
|
*/
|
|
|
|
export function toTransformStream<I, O>(
|
|
|
|
transformer: (src: ReadableStream<I>) => Iterable<O> | AsyncIterable<O>,
|
|
|
|
writableStrategy?: QueuingStrategy<I>,
|
|
|
|
readableStrategy?: QueuingStrategy<O>,
|
|
|
|
): TransformStream<I, O> {
|
|
|
|
const {
|
|
|
|
writable,
|
|
|
|
readable,
|
|
|
|
} = new TransformStream<I, I>(undefined, writableStrategy);
|
|
|
|
|
|
|
|
const iterable = transformer(readable);
|
|
|
|
const iterator: Iterator<O> | AsyncIterator<O> =
|
|
|
|
(iterable as AsyncIterable<O>)[Symbol.asyncIterator]?.() ??
|
|
|
|
(iterable as Iterable<O>)[Symbol.iterator]?.();
|
|
|
|
return {
|
|
|
|
writable,
|
|
|
|
readable: new ReadableStream<O>({
|
|
|
|
async pull(controller) {
|
|
|
|
let result: IteratorResult<O>;
|
|
|
|
try {
|
|
|
|
result = await iterator.next();
|
|
|
|
} catch (error) {
|
|
|
|
// Propagate error to stream from iterator
|
|
|
|
// If the stream status is "errored", it will be thrown, but ignore.
|
|
|
|
await readable.cancel(error).catch(() => {});
|
|
|
|
controller.error(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (result.done) {
|
|
|
|
controller.close();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
controller.enqueue(result.value);
|
|
|
|
},
|
|
|
|
async cancel(reason) {
|
|
|
|
// Propagate cancellation to readable and iterator
|
2023-08-25 09:04:43 +00:00
|
|
|
if (typeof iterator.throw === "function") {
|
2022-11-29 13:55:38 +00:00
|
|
|
try {
|
|
|
|
await iterator.throw(reason);
|
|
|
|
} catch {
|
|
|
|
/* `iterator.throw()` always throws on site. We catch it. */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await readable.cancel(reason);
|
|
|
|
},
|
|
|
|
}, readableStrategy),
|
|
|
|
};
|
|
|
|
}
|