util: Add promise-based stream wrapper
You'd think something like this would exist on npm somewhere.
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
import IoByteStream from "./stream";
|
||||||
|
|
||||||
|
export * from "./types";
|
||||||
|
export default IoByteStream;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
|
export default function makeReader(stm: Readable) {
|
||||||
|
let remain = Buffer.alloc(0);
|
||||||
|
let busy = false;
|
||||||
|
|
||||||
|
return function read(nbytes: number): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (busy) {
|
||||||
|
return reject(new Error("Already reading"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Synchronous completion case
|
||||||
|
//
|
||||||
|
|
||||||
|
const result = Buffer.alloc(nbytes);
|
||||||
|
const step = Math.min(remain.length, nbytes);
|
||||||
|
|
||||||
|
remain.copy(result, 0, 0, step);
|
||||||
|
remain = remain.slice(step);
|
||||||
|
|
||||||
|
if (step === nbytes) {
|
||||||
|
return resolve(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Asynchronous completion case
|
||||||
|
//
|
||||||
|
|
||||||
|
let pos = step;
|
||||||
|
let finish: () => void;
|
||||||
|
|
||||||
|
const onReadable = () => {
|
||||||
|
while (true) {
|
||||||
|
const chunk = stm.read() as (Buffer | null);
|
||||||
|
|
||||||
|
if (chunk === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const step = Math.min(result.length - pos, chunk.length);
|
||||||
|
|
||||||
|
chunk.copy(result, pos, 0, step);
|
||||||
|
remain = chunk.slice(step);
|
||||||
|
pos += step;
|
||||||
|
|
||||||
|
if (pos === result.length) {
|
||||||
|
finish();
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEnd = () => {
|
||||||
|
finish();
|
||||||
|
resolve(result.slice(0, pos));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onError = error => {
|
||||||
|
finish();
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
finish = () => {
|
||||||
|
stm.off("end", onEnd);
|
||||||
|
stm.off("error", onError);
|
||||||
|
stm.off("readable", onReadable);
|
||||||
|
busy = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
busy = true;
|
||||||
|
stm.on("end", onEnd);
|
||||||
|
stm.on("error", onError);
|
||||||
|
stm.on("readable", onReadable);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { Duplex } from "stream";
|
||||||
|
|
||||||
|
import makeReader from "./reader";
|
||||||
|
import { ByteStream } from "./types";
|
||||||
|
import makeWriter from "./writer";
|
||||||
|
|
||||||
|
export default class IoByteStream implements ByteStream {
|
||||||
|
private _stm?: Duplex;
|
||||||
|
private readonly _read: (nbytes: number) => Promise<Buffer>;
|
||||||
|
private readonly _write: (buf: Buffer) => Promise<void>;
|
||||||
|
|
||||||
|
constructor(stm: Duplex) {
|
||||||
|
this._stm = stm;
|
||||||
|
this._read = makeReader(stm);
|
||||||
|
this._write = makeWriter(stm);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this._stm !== undefined) {
|
||||||
|
this._stm.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._stm = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
read(nbytes: number): Promise<Buffer> {
|
||||||
|
if (this._stm === undefined) {
|
||||||
|
throw new Error("Stream is closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._read(nbytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(buf: Buffer): Promise<void> {
|
||||||
|
if (this._stm === undefined) {
|
||||||
|
throw new Error("Stream is closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._write(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface ReadableByteStream {
|
||||||
|
close(): void;
|
||||||
|
|
||||||
|
read(nbytes: number): Promise<Buffer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WritableByteStream {
|
||||||
|
close(): void;
|
||||||
|
|
||||||
|
write(buf: Buffer): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ByteStream = ReadableByteStream & WritableByteStream;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Writable } from "stream";
|
||||||
|
|
||||||
|
export default function makeWriter(stm: Writable) {
|
||||||
|
let busy = false;
|
||||||
|
|
||||||
|
return function write(buf: Buffer): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (busy) {
|
||||||
|
return reject(new Error("Already writing"));
|
||||||
|
}
|
||||||
|
|
||||||
|
busy = true;
|
||||||
|
|
||||||
|
stm.write(buf, error => {
|
||||||
|
busy = false;
|
||||||
|
|
||||||
|
if (error !== undefined) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user