I have a function from a certain library that returns an Observable that I want to call from another function. I have a need to propagate that Observable into multiple function calls. Here is how my code is structured:
extractSignature = (xml, signatureCount = 1) => {
const observ = this.generateDigest(signedContent, alg).pipe(map(digest => {
const sigContainer = {
alg: alg,
signature: signatureValue,
signedContent: signedContent,
digest: digest
};
console.log('sigContainer inside pipe: ');
console.log(sigContainer);
return sigContainer;
}));
return observ;
}
dissasemble(xml): Observable<SignatureContainerModel[]> {
const observables: Observable<any>[] = [];
for (let i = 1; i <= count; i++) {
const extractSigObservable = this.extractSignature(xml, i);
console.log('extractSigObs inside pipe: ');
console.log(extractSigObservable);
const observ = extractSigObservable.pipe(map(sigContainer => {
console.log('sigContainer inside pipe: ');
console.log(sigContainer);
const hashContainers: HashContainerModel[] = [];
const hashContainer: HashContainerModel = new HashContainerModel();
hashContainer.digestAlgorithm = sigContainer.alg;
hashContainer.bytes = sigContainer.digest;
hashContainers.push(hashContainer);
const signatureContainer: SignatureContainerModel = {
hashContainers: hashContainers,
signature: sigContainer.signature
};
console.log('observable inside pipe: ');
console.log(observ);
}));
observables.push(observ);
}
return forkJoin(observables);
}
verify() {
this.sigExec.dissasemble(this.fileContent).subscribe((signatureContainers: SignatureContainerModel[]) => {
// signatureContainers is [undefined] here
console.log('Sig Containers: ');
console.log(signatureContainers);
this.verifyHash(signatureContainers);
});
}
signatureContainers variable is [undefined] inside the subscribe. I'm not sure what the problem is since when I check all the logs that I wrote inside map functions they seem fine
RXJS Documentation on forkJoin:
Be aware that if any of the inner observables supplied to forkJoin error you will lose the value of any other observables that would or have already completed if you do not catch the error correctly on the inner observable. If you are only concerned with all inner observables completing successfully you can catch the error on the outside.
https://www.learnrxjs.io/learn-rxjs/operators/combination/forkjoin
There a possibility you are erroring out inside your pipe and those values are being lost.
Also, I noticed that you're not returning anything from your pipe. This could also be an issue.
Related
I'm importing an array into a module, and adding and removing items from that array. when I give a push, it adds the item to the array globally, so much so that if I use that same array in another module, it will include this item that I pushed. but when I try to filter, with that same array getting itself with the filter, it only removes in that specific module. How can I make it modify globally?
let { ignore } = require('../utils/handleIgnore');
const questions = require('./quesiton');
const AgendarCollector = async (client, message) => {
ignore.push(message.from);
let counter = 0;
const filter = (m) => m.from === message.from;
const collector = client.createMessageCollector(message.from, filter, {
max: 4,
time: 1000 * 60,
});
await client.sendText(message.from, questions[counter++]);
collector.on('start', () => {});
await collector.on('collect', async (m) => {
if (m) {
if (counter < questions.length) {
await client.sendText(message.from, questions[counter++]);
}
}
});
await collector.on('end', async (all) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
const finished = [];
if (all.size < questions) {
console.log('não terminado');
}
await all.forEach((one) => finished.push(` ${one.content}`));
await client.sendText(message.from, `${finished}.\nConfirma?`);
});
};
module.exports = AgendarCollector;
see, in this code, import the ignore array and i push an item to then when the code starts and remove when its end.
but the item continues when I check that same array in another module.
I tried to change this array ignore by using functions inside this module but still not working
let ignore = [];
const addIgnore = (message) => {
ignore.push(message.from);
};
const removeIgnore = (message) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
};
console.log(ignore);
module.exports = { ignore, addIgnore, removeIgnore };
You are using the variables for import and export and hence cought up with issues.
Instead, make use of getters.
Write a function which will return the array of ignore. something like this:
const getIgnoredList = () => {
return ignore;
};
and in your first code, import getIgnoredList and replace ignore with getIgnoredList()
Explanation :
Whenever we import the variables only the value at that particular time will be imported and there will not be any data binding. Hence there won't be any change in the data even though you think you are updating the actual data.
When you use require(...) statement it's executed only once. Hence when you try to access the property it gives the same value everytime.
Instead you should use getters
let data = {
ignore : [],
get getIgnore() {
return this.ignore
}
}
module.export = {
getIgnore: data.getIgnore,
}
Then wherever you want to access ignore do
var {getIgnore}= require('FILE_NAME')
Now: console.log(getIgnore) will invoke the getter and give you current value of ignore
Using getters will allow you to access particular variables from other modules but if you want to make changes in value of those variables from other module you have to use setter.
More about getters here
More about setters here
I've come across a weird issue where a new variable is being created in local scope even if it is defined outside,
from the below code
after I call buildMeta() and check the contents of "data", it is always empty
implying that it's not being modified at all, even if I've specifically targeted "that.data" where that refers to the class' object.
I'd appreciate if anyone would point out what I am doing wrong.
class meta {
constructor(files) {
if(!files) throw Error("files not specified");
this.data = {};
this.ls = files;
}
buildMeta() {
var that = this;
for(let i = 0; i < that.ls.length; i++) {
mm.parseFile(that.ls[i]).then(x => {
var info = x.common;
that.data[info.artist] = "test";
}).catch((x) => {
console.log(x);
});
}
}
}
const mm = new meta(indexer); // indexer is an array of file paths
mm.buildMeta();
console.log(mm.data);
You're mixing sync with async code here.
The for loop won't wait for the parseFile promises to resolve.
You could use Promise.all to fill in the data when the files are parsed.
// Class names should be written using a capital letter
class Meta {
...
buildMeta() {
// You don't need this assignment since you're using arrow functions
// var that = this;
const promises = this.ls.map(filePath => mm.parseFile(filePath));
return Promise.all(promises).then(resolvedPromises => {
resolvedPromises.map(({ parsedFile }) => {
this.data[parsedFile.common.artist] = "test";
});
return this.data;
}).catch(console.error);
}
...
const mm = new meta(indexer); // indexer is an array of file paths
mm.buildMeta().then(data => {console.log(data)});
Hope this helps.
You are logging mm.data before parseFile has finished. Your code implies that it returns a promise, so your insertion into that.data will happen after your console.log(mm.data) executes.
You need to return a promise from buildMeta, so that you can do...
const mm = new meta(indexer);
mm.buildMeta().then(() => {
console.log(mm.data);
})
Here's a buildMeta that should do what you need. This returns a promise that waits for all of the parseFile invocations to do their work and update this.data...
buildMeta() {
return Promise.all(this.ls.map(f => mm.parseFile(f).then(x => {
var info = x.common;
this.data[info.artist] = "test";
})))
}
I am writing a custom operator to load a csv file and emit each line as data. This operator is supposed to work like the of operator, which is a static function to create observable. I follow the instruction of operator creation and add the operator function directly to Observable prototype.
All following code is written in JavaScript ES6.
My source code is this
import { Observable } from 'rxjs';
import { createInterface } from 'readline';
import { createReadStream } from 'fs';
function fromCSV(path, options) {
return Observable.create((subscriber) => {
let readStream = createReadStream(path);
let reader = createInterface({ input: readStream });
let isHeader = true;
let columns;
reader.on('line', (line) => {
if (isHeader) {
columns = line.split(',');
isHeader = false;
} else {
let values = line.split(',');
let valueObject = {};
for (let i = 0, n = columns.length; i < n; i++) {
valueObject[columns[i]] = values[i] || undefined;
}
subscriber.next(valueObject);
}
});
reader.on('close', () => subscriber.complete());
readStream.on('error', (error) => subscriber.error(error));
});
}
Observable.prototype.fromCSV = fromCSV;
The operator function looks totally correct, but when I try to use this operator like
import { Observable } from 'rxjs';
import './rx-from-csv';
Observable.fromCSV(testCSV)
.subscribe((row) => {
console.log(row);
});
It throws an error TypeError: _rxjs.Observable.fromCSV is not a function. So the function binding fails and I have no idea why it happens :-( Any help is appreciated.
This particularly confuses me because I have successfully done a similar operator binding for another custom csv operator.
The problem is that TypeScript doesn't know about the operator because it couldn't find it in RxJS's *.d.ts.
Have a look at how it's done by the default RxJS operators: https://github.com/ReactiveX/rxjs/blob/master/src/add/operator/bufferCount.ts
In you case you'll need just the declare module ... part with a correct path to the Observable definition. For example:
function fromCSV(path, options) {
...
}
Observable.prototype.fromCSV = fromCSV;
declare module 'rxjs/Observable' {
interface Observable<T> {
fromCSV: typeof fromCSV;
}
}
It turns out that I used a wrong way to add static function. See this post for more information.
To add a static function to the Observable class, the code needs to be
Observable.fromCSV = fromCSV;
Adding the function to the class's prototype will make it only available after newing that class.
I create my own Observable and subscribed two functions to it. I would expect to have both functions executed for each element in the sequence but only the last one is.
let observer = null
const notificationArrayStream = Rx.Observable.create(function (obs) {
observer = obs;
return () => {}
})
function trigger(something) {
observer.next(something)
}
notificationArrayStream.subscribe((x) => console.log('a: ' + x))
notificationArrayStream.subscribe((x) => console.log('b: ' + x))
trigger('TEST')
Expected output
a: TEST
b: TEST
Actual output
b: TEST
Here's the JSBin: http://jsbin.com/cahoyey/edit?js,console
Why is that? How can I have multiple functions subscribed to a single Observable?
Subject
In your case, you could simply use a Subject. A subject allows you to share a single execution with multiple observers when using it as a proxy for a group of subscribers and a source.
In essence, here's your example using a subject:
const subject = new Subject();
function trigger(something) {
subject.next(something);
}
subject.subscribe((x) => console.log('a: ' + x));
subject.subscribe((x) => console.log('b: ' + x));
trigger('TEST');
Result:
a: TEST
b: TEST
Pitfall: Observers arriving too late
Note that the timing of when you subscribe and when you broadcast the data is relevant. If you send a broadcast before subscribing, you're not getting notified by this broadcast:
function trigger(something) {
subject.next(something);
}
trigger('TEST');
subject.subscribe((x) => console.log('a: ' + x));
subject.subscribe((x) => console.log('b: ' + x));
Result: (empty)
ReplaySubject & BehaviorSubject
If you want to ensure that even future subscribers get notified, you can use a ReplaySubject or a BehaviorSubject instead.
Here's an example using a ReplaySubject (with a cache-size of 5, meaning up to 5 values from the past will be remembered, as opposed to a BehaviorSubject which can remember only the last value):
const subject = new ReplaySubject(5); // buffer size is 5
function trigger(something) {
subject.next(something);
}
trigger('TEST');
subject.subscribe((x) => console.log('a: ' + x));
subject.subscribe((x) => console.log('b: ' + x));
Result:
a: TEST
b: TEST
To have multiple functions subscribe to a single Observable, just subscribe them to that observable, it is that simple. And actually that's what you did.
BUT your code does not work because after notificationArrayStream.subscribe((x) => console.log('b: ' + x)) is executed, observer is (x) => console.log('b: ' + x)), so observer.next will give you b: TEST.
So basically it is your observable creation which is wrong. In create you passed an observer as parameter so you can pass it values. Those values you need to generate somehow through your own logic, but as you can see your logic here is erroneous. I would recommend you use a subject if you want to push values to the observer.
Something like:
const notificationArrayStream = Rx.Observable.create(function (obs) {
mySubject.subscribe(obs);
return () => {}
})
function trigger(something) {
mySubject.next(something)
}
Every time you subscribe, you are overriding the var observer.
The trigger function only reference this one var, hence no surprise there is only one log.
If we make the var an array it works as intended: JS Bin
let obs = [];
let foo = Rx.Observable.create(function (observer) {
obs.push(observer);
});
function trigger(sth){
// console.log('trigger fn');
obs.forEach(ob => ob.next(sth));
}
foo.subscribe(function (x) {
console.log(`a:${x}`);
});
foo.subscribe(function (y) {
console.log(`b:${y}`);
});
trigger(1);
trigger(2);
trigger(3);
trigger(4);
A cleaner solution would be to use Subject, as suggested above.
Observables are not multicasting; unless you use any kind of Subject. You can of course create Subject, pipe the Observable output into like other answers propose.
However if you already have an Observalbe, it is way more convenient to use share() that turns Observable into Subject or shareReplay(n) which would be equivalent for ReplaySubject(n):
import {share} from 'rxjs/operators';
let observer = null
const notificationArrayStream = new Observable(obs => {
observer = obs;
}).pipe(share());
function trigger(something) {
observer.next(something)
}
notificationArrayStream.subscribe((x) => console.log('a: ' + x))
notificationArrayStream.subscribe((x) => console.log('b: ' + x))
trigger('TEST')
That's pretty much it.
You can build wrapper class Subscribable<> based on ReplaySubject. It would be cleaner than managing Subject and Observable:
export class Subscribable<T> {
private valueSource: Subject = new ReplaySubject(1);
public value: Observable;
private _value: T;
constructor() {
this.value = this.valueSource.asObservable();
}
public set(val: T) {
this.valueSource.next(val);
this._value = val;
}
public get(): T {
return this._value;
}
}
Usage:
let arrayStream : Subscribable<TYPE> = new Subscribable<TYPE>();
…
public setArrayStream (value: TYPE) {
this.set(value);
}
Handle value change:
arrayStream.value.subscribe(res => { /*handle it*/ });
Original article: http://devinstance.net/articles/20170921/rxjs-subscribable
Instead of using a Subject, it is also possible to use the publishReplay() + refCount() combo to allow an observable to multicast to multiple subscribers:
const notificationArrayStream = Rx.Observable.create(function (obs) {
observer = obs;
return () => {}
}).pipe(publishReplay(), refCount())
const subs = []
const ob = new Observable((s) => {
console.log('called')
subs.push(s)
})
const trigger = (v) => {
subs.forEach((sub) => {
sub.next(v)
})
}
ob.subscribe((v) => {
console.log('ob1', v)
})
ob.subscribe((v) => {
console.log('ob2', v)
})
trigger(1)
Change your code into something like this, and it will work. The point here is that each subscription is updated through its corresponding subscriber, if you have multiple subscriptions, you have to notify multiple subscribers. And in your case, you just notified the last one.
It seems in rxjs 4.x, Rx.Observable.fromCallback accept scope as the second parameter, but in 5.0, this method is changed to Rx.Observable.bindCallback and doesn't accept scope parameter. How to add scope parameter in bindCallback. For example in ES6.
class Test {
constructor(input) {
this.input = input;
}
callback(cb) {
return cb(this.input);
}
rx() {
// this works on rx 4.x
// var observable = Rx.Observable.fromCallback(this.callback, this)();
// this doesn't work, because this.callback function doesn't use original this, so cannot get this.input
var observable = Rx.Observable.bindCallback(this.callback)();
// Work around: Rx.Observable.bindCallback(this.callback)();
// var me = this;
// var observable = Rx.Observable.bindCallback((cb) => {me.callback(cb);})();
observable.subscribe(
input => console.log('get data => ' + input),
err => console.log('get error =>' + err),
() => console.log('complete')
);
}
}
new Test(100).rx();
There is an example at http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#static-method-bindCallback which shows how to do this.
Use bindCallback on object method
const boundMethod = Rx.Observable.bindCallback(someObject.methodWithCallback);
boundMethod.call(someObject) // make sure methodWithCallback has access to someObject
.subscribe(subscriber);
You can call it immediately without declaring a variable, and also pass args like this:
Rx.Observable.bindCallback(someObject.callback).call(someObject,<args>)
So to bind to this you can simply call
Rx.Observable.bindCallback(this.callback).call(this,<args>)
It works for me, when I add this to the constructor
constructor(input) {
this.input = input;
this.callback = this.callback.bind(this)
}