NodeJS streams not awaiting async - node.js

I have run into an issue when testing NodeJS streams. I can't seem to get my project to wait for the output from the Duplex and Transform streams after running a stream.pipeline, even though it is returning a promise. Perhaps I'm missing something, but I believe that the script should wait for the function to return before continuing. The most important part of the project I'm trying to get working is:
// Message system is a duplex (read/write) stream
export class MessageSystem extends Duplex {
constructor() {
super({highWaterMark: 100, readableObjectMode: true, writableObjectMode: true});
}
public _read(size: number): void {
var chunk = this.read();
console.log(`Recieved ${chunk}`);
this.push(chunk);
}
public _write(chunk: Message, encoding: string,
callback: (error?: Error | null | undefined, chunk?: Message) => any): void {
if (chunk.data === null) {
callback(new Error("Message.Data is null"));
} else {
callback();
}
}
}
export class SystemStream extends Transform {
public type: MessageType = MessageType.Global;
public data: Array<Message> = new Array<Message>();
constructor() {
super({highWaterMark: 100, readableObjectMode: true, writableObjectMode: true});
}
public _transform(chunk: Message, encoding: string,
callback: TransformCallback): void {
if (chunk.single && (chunk.type === this.type || chunk.type === MessageType.Global)) {
console.log(`Adding ${chunk}`);
this.data.push(chunk);
chunk = new Message(chunk.data, MessageType.Removed, true);
callback(undefined, chunk); // TODO: Is this correct?
} else if (chunk.type === this.type || chunk.type === MessageType.Global) { // Ours and global
this.data.push(chunk);
callback(undefined, chunk);
} else { // Not ours
callback(undefined, chunk);
}
}
}
export class EngineStream extends SystemStream {
public type: MessageType = MessageType.Engine;
}
export class IOStream extends SystemStream {
public type: MessageType = MessageType.IO;
}
let ms = new MessageSystem();
let es = new EngineStream();
let io = new IOStream();
let pipeline = promisify(Stream.pipeline);
async function start() {
console.log("Running Message System");
console.log("Writing new messages");
ms.write(new Message("Hello"));
ms.write(new Message("world!"));
ms.write(new Message("Engine data", MessageType.Engine));
ms.write(new Message("IO data", MessageType.IO));
ms.write(new Message("Order matters in the pipe, even if Global", MessageType.Global, true));
ms.end(new Message("Final message in the stream"));
console.log("Piping data");
await pipeline(
ms,
es,
io
);
}
Promise.all([start()]).then(() => {
console.log(`Engine Messages to parse: ${es.data.toString()}`);
console.log(`IO Messages to parse: ${io.data.toString()}`);
});
Output should look something like:
Running message system
Writing new messages
Hello
world!
Engine Data
IO Data
Order Matters in the pipe, even if Global
Engine messages to parse: Engine Data
IO messages to parse: IO Data
Any help would be greatly appreciated. Thanks!
Note: I posted this with my other account, and not this one that is my actual account. Apologies for the duplicate.
Edit: I initially had the repo private, but have made it public to help clarify the answer. More usage can be found on the feature/inital_system branch. It can be run with npm start when checked out.
Edit: I've put my custom streams here for verbosity. I think I'm on a better track than before, but now getting a "null" object recieved down the pipeline.

As the documentation states, stream.pipeline is callback-based doesn't return a promise.
It has custom promisified version that can be accessed with util.promisify:
const pipeline = util.promisify(stream.pipeline);
...
await pipeline(...);

After some work of the past couple of days, I've found my answer. The issue was my implementation of the Duplex stream. I have since changed the MessageSystem to be a Transform stream to be easier to manage and work with.
Here is the product:
export class MessageSystem extends Transform {
constructor() {
super({highWaterMark: 100, readableObjectMode: true, writableObjectMode: true});
}
public _transform(chunk: Message, encoding: string,
callback: TransformCallback): void {
try {
let output: string = chunk.toString();
callback(undefined, output);
} catch (err) {
callback(err);
}
}
}
Thank you to #estus for the quick reply and check. Again, I find my answer in the API all along!
An archived repository of my findings can be found in this repository.

Related

NestJs #Sse - event is consumed only by one client

I tried the sample SSE application provided with nest.js (28-SSE), and modified the sse endpoint to send a counter:
#Sse('sse')
sse(): Observable<MessageEvent> {
return interval(5000).pipe(
map((_) => ({ data: { hello: `world - ${this.c++}` }} as MessageEvent)),
);
}
I expect that each client that is listening to this SSE will receive the message, but when opening multiple browser tabs I can see that each message is consumed only by one browser, so if I have three browsers open I get the following:
How can I get the expected behavior?
To achieve the behavior you're expecting you need to create a separate stream for each connection and push the data stream as you wish.
One possible minimalistic solution is below
import { Controller, Get, MessageEvent, OnModuleDestroy, OnModuleInit, Res, Sse } from '#nestjs/common';
import { readFileSync } from 'fs';
import { join } from 'path';
import { Observable, ReplaySubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Response } from 'express';
#Controller()
export class AppController implements OnModuleInit, OnModuleDestroy {
private stream: {
id: string;
subject: ReplaySubject<unknown>;
observer: Observable<unknown>;
}[] = [];
private timer: NodeJS.Timeout;
private id = 0;
public onModuleInit(): void {
this.timer = setInterval(() => {
this.id += 1;
this.stream.forEach(({ subject }) => subject.next(this.id));
}, 1000);
}
public onModuleDestroy(): void {
clearInterval(this.timer);
}
#Get()
public index(): string {
return readFileSync(join(__dirname, 'index.html'), 'utf-8').toString();
}
#Sse('sse')
public sse(#Res() response: Response): Observable<MessageEvent> {
const id = AppController.genStreamId();
// Clean up the stream when the client disconnects
response.on('close', () => this.removeStream(id));
// Create a new stream
const subject = new ReplaySubject();
const observer = subject.asObservable();
this.addStream(subject, observer, id);
return observer.pipe(map((data) => ({
id: `my-stream-id:${id}`,
data: `Hello world ${data}`,
event: 'my-event-name',
}) as MessageEvent));
}
private addStream(subject: ReplaySubject<unknown>, observer: Observable<unknown>, id: string): void {
this.stream.push({
id,
subject,
observer,
});
}
private removeStream(id: string): void {
this.stream = this.stream.filter(stream => stream.id !== id);
}
private static genStreamId(): string {
return Math.random().toString(36).substring(2, 15);
}
}
You can make a separate service for it and make it cleaner and push stream data from different places but as an example showcase this would result as shown in the screenshot below
This behaviour is correct. Each SSE connection is a dedicated socket and handled by a dedicated server process. So each client can receive different data.
It is not a broadcast-same-thing-to-many technology.
How can I get the expected behavior?
Have a central record (e.g. in an SQL DB) of the desired value you want to send out to all the connected clients.
Then have each of the SSE server processes watch or poll that central record
and send out an event each time it changes.
you just have to generate a new observable for each sse connection of the same subject
private events: Subject<MessageEvent> = new Subject();
constuctor(){
timer(0, 1000).pipe(takeUntil(this.destroy)).subscribe(async (index: any)=>{
let event: MessageEvent = {
id: index,
type: 'test',
retry: 30000,
data: {index: index}
} as MessageEvent;
this.events.next(event);
});
}
#Sse('sse')
public sse(): Observable<MessageEvent> {
return this.events.asObservable();
}
Note: I'm skipping the rest of the controller code.
Regards,

ZeroMQ retransmitting pub-sub model

I'm trying to use the ZeroMQ javascript bindings to implement a re-transmitting pub-sub backbone.
Something to the effect of:
inbound modules announce themselves to the backbone, the backbone subscribes to them and re-transmits on its own publisher socket
outbound modules subscribe to backbone
I'm running into an issue with the pub sockets required to be used from a single thread though.
My current code, was something like this:
async function listen(name: string, sub: zmq.Subscriber, pub: zmq.Publisher) {
let id = 0;
for await (const [topic, msg] of sub) {
console.log(`BACKBONE | ${name} | received a message id: ${++id} related to: ${topic.toString()} containing message: ${msg.toString()}`);
await pub.send([topic, msg]);
}
}
Which gets instantiated for each of the inbound modules, but then of course they clash on pub.send.
I wrote a queue to serialize the socket access, which resolved the problem.
import * as zmq from "zeromq"
export class PublisherQueue {
private queue: Buffer[][] = []
constructor(private publisher: zmq.Publisher) { }
private static toBuffer(value: Buffer | string): Buffer {
if (value instanceof Buffer) {
return value as Buffer;
} else {
return Buffer.from(value as string);
}
}
send(topic: Buffer | string, msg: Buffer | string) {
this.queue.push([PublisherQueue.toBuffer(topic), PublisherQueue.toBuffer(msg)]);
}
async run() {
while (true) {
if (this.queue.length > 0) {
let msg = this.queue.shift();
if (msg !== undefined) {
await this.publisher.send(msg);
}
} else {
await new Promise(resolve => setTimeout(resolve, 50));
}
}
}
}

Puppeteer mock page request object

import { Page } from 'puppeteer/lib/Page';
export class MonitorRequestHelper {
public static monitorRequests(page: Page, on = false) {
if(on) {
page.on('request', req => {
if (['image', 'font', 'stylesheet'].includes(req.resourceType())) {
// Abort requests for images, fonts & stylesheets to increase page load speed.
req.abort();
} else {
req.continue();
}
});
} else {
return true;
}
}
}
I am trying to mock and spy the function to check if it got called at least once.
Also, it would be helpful if some explain me how to mock and spy event-emitter object.
The source code is available on https://github.com/Mukesh23singh/puppeteer-unit-testing
If you want to test that your logic in monitorRequests works, you need to pass in a fake Page object with an event emitter interface that produces a fake request that you can test on.
Something like:
import {spy} from 'sinon';
// Arrange
const fakePage = { on(type, cb) { this[type] = cb; } }; // "event emitter"
const fakeRequest = {
abort: sinon.spy(),
resourceType() { return 'image'; }
};
monitorRequests( fakePage, true );
// Act
// trigger fake request
fakePage['request'](fakeRequest);
// Assert
assert(fakeRequest.abort.called);

Operators invoked multiple times for merged Observable although only one source emits

I have a function in which I'm calling an instance of Manager's onSpecificData() to which I'm subscribing in order to update my application's state (I'm managing a state on the server-side as well).
The problem is that in the SomeManager's implementation of onSpecificData() I'm merging 3 different Observables using merge() operator, which for some reason triggers the invocation of all the underlying Observable's operators even though only 1 of the sources is the one that's emitting a value
SomeManager.ts
export class DerivedManager implements Manager {
private driver: SomeDriver;
constructor(...) {
this.driver = new SomeDriver(...);
}
public onSpecificData(): Observable<DataType> {
return merge(
this.driver.onSpecificData(Sources.Source1).map((value) => {
return {source1: value};
}),
this.driver.onSpecificData(Sources.Source2).map((value) => {
return {source2: value};
}),
this.driver.onSpecificData(Sources.Source3).map((value) => {
return {source3: value};
})
);
}
Manager.ts
export type DataType = Partial<{value1: number, value2: number, value3: number}>;
export interface Manager {
onSpecificData(): Observable<DataType>;
}
SomeDriver.ts
export const enum Sources {
Source1,
Source2,
Source3,
}
export class SomeDriver extends Driver {
private static specificDataId = 1337; // some number
private handler: Handler;
constructor(...) {
super(...);
this.handler = new Handler(this.connection, ...);
// ...
}
// ...
onSpecificData(source: Sources): Observable<number> {
return this.handler
.listenToData<SpecificDataType>(
SomeDriver.specificDataId,
(data) => data.source === source)
).map((data) => data.value);
}
}
Driver.ts
export abstract class Driver {
protected connection: Duplex;
constructor(...) {
// init connection, etc...
}
public abstract onSpecificData(source: number);
// some implementations and more abstract stuff...
}
Handler.ts
export class Handler {
private data$: Observable<Buffer>;
constructor(private connection: Duplex, ...) {
this.data$ = Observable.fromEvent<Buffer>(connection as any, 'data');
}
listenToData<T>(dataId: number, filter?: (data: T) => boolean) {
return this.data$
.map((data) => {
// decode and transform
})
.filter((decodedData) => !decodedData.error && decodedData.value.id)
.do((decodedData) => {
console.log(`Got ${decodedData.value.id}`);
})
.map((decodedData) => decodedData.value.value as T)
.filter(filter || () => true);
}
}
And finally, subscribe()-ing:
export default function(store: Store<State>, manager: Manager) {
// ...
manager.onSpecificData()
.subscribe((data) => {
// update state according to returned data
});
}
As you can see, there is only 1 underlying Observable (data$) but apparently the operator chain in listenToData<T>() is invoked 3 times for each value emitted by it. I already know this is because of SomeManager#onSpecificData()'s merge of those 3 Observables, but I don't know why this happens. I want it to be invoked once for each value.
Help will be much appreciated.
I solved this in a "hacky" way, in my opinion. I replaced data$ with a Subject, created an observable from stream's 'data' event, moving all the shared logic to that observable and emit a value from the subject, like so:
export class Handler {
private dataSrc = new Subject<DecodedData>();
constructor(private connection: Duplex, ...) {
Observable.fromEvent<Buffer>(connection as any, 'data')
.map((data) => {
// decode and transform
})
.filter((decodedData) => !decodedData.error)
.do((decodedData) => {
console.log(`Got ${decodedData.value.id}`);
})
.subscribe((decodedData) => {
this.dataSrc.next(decodedData);
});
}
listenToData<T>(dataId: number, filter?: (data: T) => boolean) {
return this.dataSrc
.filter((decodedData) => decodedData.value.id === dataId)
.map((decodedData) => decodedData.value.value as T)
.filter(filter || () => true);
}
}
Not exactly the solution I was looking for, but it works. If anyone has a better solution, which better suits the "Rx way" to do stuff, I'd love to hear it.

Extend writeable stream to become transform stream?

I'm trying to wrap my head around how you would do this, because you can't just inherit from transform and it doesn't seem like you can just inherit from this particular writeable stream.
Ideally it'd be something like:
const Writeable = require('Writeable');
class Transform extends Writeable {
constructor() {
super();
}
_transform(chunk, encoding) {
}
}
But I can just tell that won't work. The particular writeable stream I'm trying to inherit from would be the tapjs/tap-parser. I'd ideally be able to leverage the event listeners inside of it to parse TAP output.
Hmm, you wrote about Writable, but pasted code for Transform, not sure if you just made a mistake, but if you did this on purpose here is an example for overnighting Transform.
Where line is your data, that you can play with before passing it forward.
let Transform = require("stream").Transform;
class awesome_class_name extends Transform
{
constructor()
{
super()
}
_transform (line, encoding, processed) {
//
// Add the data that came in, to the output stream
//
this.push(line);
//
// We let system know that we finished processing the data.
//
processed();
}
}
I'm not sure about es6 syntax, but here is a traditional Transform stream that receives TAP output and outputs objects describing the various parsed parts:
const Transform = require('stream').Transform;
const inherits = require('util').inherits;
const Parser = require('tap-parser');
function MyTransform() {
const self = this;
this._parser = new Parser();
Transform.call(this, { readableObjectMode: true });
this._parser.on('complete', function(results) {
self.push({ type: 'complete', results });
}).on('assert', function(assert) {
self.push({ type: 'assert', assert });
}).on('comment', function(comment) {
self.push({ type: 'comment', comment });
}).on('plan', function(plan) {
self.push({ type: 'plan', plan });
}).on('version', function(version) {
self.push({ type: 'version', version });
}).on('bailout', function(reason) {
self.push({ type: 'bailout', reason });
}).on('extra', function(extra) {
self.push({ type: 'extra', extra });
});
}
inherits(MyTransform, Transform);
MyTransform.prototype._write = function(chunk, encoding, cb) {
this._parser.write(chunk, cb);
};
MyTransform.prototype._flush = function(cb) {
const self = this;
this._parser.end(function() {
self.push(null);
cb();
});
};

Resources