I have this method that I copied from a websocket tutorial but I don't understand the meaning of the "return () => { ... }" inside the observable ? Can someone explain me what is the purpose of that ?
public onMessage(topic: string, handler = SocketClientService.jsonHandler) : Observable<any> {
return this.connect().pipe(first(), switchMap(client => {
return new Observable<any>(observer => {
const subscription : StompSubscription = client.subscribe(topic, message => {
observer.next(handler(message));
});
return () => {
console.log("Unsubscribe from socket-client service");
client.unsubscribe(subscription .id);
}
});
}));
}
In order to create an Observable, you can use new Observable or a creation operator. See the following example:
const observable = new Observable(function subscribe(subscriber) {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
});
You can provide a function unsubscribe() to allow dispose of resources, and that function goes inside subscribe() as follows:
const observable = new Observable(function subscribe(subscriber) {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
return function unsubscribe() {
console.log('Clearing resources on observable');
};
});
Of course, you can use an arrow function expression to have:
const observable = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.next(3);
return () => {
console.log('Clearing resources on observable');
};
});
Try the following code to test the Observable:
const subscription = observable.subscribe(res => console.log('observable data:', res));
subscription.unsubscribe();
Finally, subscription.unsubscribe() is going to remove the socket connection in your example.
Find a project running with these examples here: https://stackblitz.com/edit/typescript-observable-unsubscribe
Let me know if that helps!
Related
I have a click that calls the method:
public clickEvent() {
this.createIframe().then((iframe) => { // Return iframe or create if is not before needed inside
// Async hard logic here
})
}
Problem is when user clicks a lot of times clickEvent() it fires promise and then fires a hard logic inside.
How to avoid click until logic inside is not finished?
Or disable to call logic inside if it is done?
If you're using Angular, I think you can convert the click event into an observable and then use the variety of operators such as exhaustMap to achieve this.
import { exhaustMap, fromEvent } from 'rxjs';
....
#ViewChild('btnId') btnElementRef!: ElementRef<HTMLButtonElement>;
ngAfterViewInit(): void {
fromEvent(this.btnElementRef.nativeElement, 'click')
.pipe(
exhaustMap(() => this.createIframe())
)
.subscribe((iframe) => {
// hard coded async logic here
});
}
);
This will ignore sub-sequent click until the Promise resolve first.
Further more, if you want to disable the button and display somekind of loading indicator, you can also add a variable to track that inside the stream using tap
fromEvent(this.btnElementRef.nativeElement, 'click')
.pipe(
tap(() => isProcessing = true),
exhaustMap(() => this.createIframe())
)
.subscribe((iframe) => {
isProcessing = false;
// hard coded async logic here
});
Make createIframe cache its Promise (like as an instance property), and return that first if it exists, instead of starting another. For example:
// example function that creates the Promise
const createPromise = () => {
console.log('creating Promise');
return new Promise(resolve => setTimeout(resolve, 3000));
}
class SomeClass {
createIframe() {
if (this.iframePromise) return this.iframePromise;
this.iframePromise = createPromise();
return this.iframePromise;
}
clickEvent() {
this.createIframe().then((iframe) => {
console.log('clickEvent has received the Promise and is now running more code');
})
}
}
const s = new SomeClass();
button.onclick = () => s.clickEvent();
<button id="button">click to call clickEvent</button>
If you also want to prevent // Async hard logic here from running multiple times after multiple clicks, assign something to the instance inside clickEvent instead.
// example function that creates the Promise
const createPromise = () => {
console.log('creating Promise');
return new Promise(resolve => setTimeout(resolve, 3000));
}
class SomeClass {
createIframe() {
return createPromise();
}
clickEvent() {
if (this.hasClicked) return;
this.hasClicked = true;
this.createIframe().then((iframe) => {
console.log('clickEvent has received the Promise and is now running more code');
})
}
}
const s = new SomeClass();
button.onclick = () => s.clickEvent();
<button id="button">click to call clickEvent</button>
I'm building a react app that uses Firebase and Firestore.
I'm using the onSnapshot function so I get the real time data from Firestore, but I was wondering how I can remove that listener.
Yeah I know, I must use the cleanup function of the useEffect hook, but I can't make it work, here's my code:
useEffect(() => {
let removeListener = getCanzoni(utente).onSnapshot(function (querySnapshot) {
let promises = querySnapshot.docs.map(async function (doc) {
let canzone = doc.data();
canzone.id = doc.id;
return canzone;
});
Promise.all(promises).then((canzoni) => {
cambiaCanzoni(canzoni);
});
return function cleanup() {
console.log("Removed Listener");
removeListener();
};
});
}, []);
The getCanzoni function is imported from another files and it's definition is:
export function getCanzoni(utente) {
return firestore
.collection("Canzoni")
.where("utente", "==", utente.uid)
.orderBy("data", "desc");
}
When I remove the component, I don't see the 'Removed Listener' in the console.
I know that the clean-up function is called when the dependency array changes or when the components is unmounted.
Any idea or tips?
I've found the error:
The clean up function must be defined in the hook's function body, not inside other function, like this:
useEffect(() => {
let removeListener = getCanzoni(utente).onSnapshot(function (querySnapshot) {
let promises = querySnapshot.docs.map(async function (doc) {
let canzone = doc.data();
canzone.id = doc.id;
return canzone;
});
Promise.all(promises).then((canzoni) => {
cambiaCanzoni(canzoni);
});
});
return function cleanup() {
console.log("Tolto il listener");
removeListener();
};
}, []);
I know how to emit a value to the observer and subscribe to them using observable, as shown here
var observable = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
}).subscribe((success) => {
console.log(success);
})
but if I want to do the same thing with the function, ie. I have something like this, then how can I achieve it
var observable = new Observable(observer => {
observer.next(function () {
setTimeout(() => {
1
}, 1000)
})
observer.next(function () {
setTimeout(() => {
2
}, 1000)
})
observer.next(function () {
setTimeout(() => {
3
}, 1000)
})
}).subscribe((success) => {
console.log(success);
})
is it possible, all I have to do is call a series of async functions, how can I do it
UPDATE
i want to call a series of asnc fuctions in a sequence, ie. the second should be called only after the completion of the first functions operation and so on and so forth
You can do something like this. This is just the fundamental here. You can call your async instead of emitting static values.
var ParentObservable = new Observable();
ParentObservable.subscribe((res) => {
//res is your response from async calls
//Call asyncCall again from here
})
function asyncCall(){
this.http.get("your URL").map((res)=> res.json()).subscribe((res)=>{
ParentObservable.next(res);
})
}
Im testing a publish method on a pub sub class. I am creating a callback function within the beforeEach function and subscribing to the class. In the it method I am publishing the event and attempting to test that the callback was called which is basically how the class works. I have got the test working and it passes but the problem is I had to use a setTimeout to get this to work. I believe this is probably not the right way to do this.
describe('publish', () => {
let testpublish;
let callback;
beforeEach(() => {
callback = function(data) { return data + 10; }
testpublish = {
'id': 'testpublish1',
'event': 'testpublish',
'callback': callback
};
subject.subscribe(testpublish);
});
it('should call the subscription function', () => {
subject.publish('testpublish', 9);
setTimeout(() => {
expect(callback).toEqual(19);
});
});
});
I initially wanted to spy on the callback just to see if it was called but the documentation for Jasmine says I must place my method in an object:
spyOn(obj, methodName) → {Spy}
Any advice on a better way to do this would be appreciated. Thanks.
PubSub Class if useful ??
#Injectable()
export class Pubsub {
private events: any = {};
public subscribe(config: any) {
let event = config['event'];
this.events[event] = this.events[event] || [];
if (this.events[event].length < 1) {
this.events[event].push(config);
} else {
for (let i = 0; i < this.events[event].length; i++) {
if (this.events[event][i].id !== config.id) {
this.events[event].push(config);
}
}
}
}
public unsubscribe(obj: Object) {
let event = obj['event'];
let id = obj['id'];
if (this.events[event]) {
this.events[event] = this.events[event].filter((eventObj) => {
return eventObj.id !== id;
});
}
if (this.events[event].length === 0) {
delete this.events[event];
}
}
public publish(event: string, data: any) {
if (this.events[event]) {
this.events[event].forEach(function(obj) {
obj.callback(data);
});
}
}
public getEvents() {
return this.events;
}
}
Existing function cannot be spied, because a spy is a new function, and the reference to original function is already used in the place where it is being called.
Considering that callback function is defined in the test itself, not inside the application, it should be defined as a spy in the first place:
callback = jasmine.createSpy();
It doesn't even have to do something because its return value doesn't add value to the test.
And it is tested like
const arg = {};
subject.publish('testpublish', arg);
expect(callback.calls.count()).toBe(1);
expect(callback.calls.first().args[0]).toBe(arg);
publish is synchronous, as well the rest of the class. There's no need for setTimeout, and it is harmful here. When done parameter isn't specified for the test, it is considered synchronous, and setTimeout makes assertions ignored in this test.
This
it('should pass', () => {
setTimeout(() => {
expect(1).toBe(2);
});
});
will always pass. And only if if the suite has no other tests, this will trigger SPEC HAS NO EXPECTATIONS warning.
jasmine.createSpy('spy') will do work.
describe('publish', () => {
let testpublish;
let callback;
let subject = new Pubsub();
beforeEach(() => {
callback = function (data) {
return data + 10;
}
testpublish = {
'id': 'testpublish1',
'event': 'testpublish',
'callback': jasmine.createSpy('spy')
};
subject.subscribe(testpublish);
});
it('should call the subscription function', () => {
subject.publish('testpublish', 9);
expect(testpublish.callback).toHaveBeenCalledWith(9);
});
});
I am mocking a Sequelize find call, and then modifying the result before returning the object in a Promise.resolve();
Here's my simplified code;
test.js
test('can pass test', t => {
let mock_instance = models.myModel.build({
a_property: 5
});
let Stub = sandbox.stub(models.myModel, 'find', () => {
return Promise.resolve(mock_instance);
});
models.myModel.functionCall(mock_instance.id).then(returned_object => {
let expected = {
a_property: 6
};
t.equal(returned_object.get({plain:true}), expected);
// Expected { a_property: 6 }
// Actual { a_property: 5 }
Stub.restore();
t.end();
});
})
model.js // Class method
classFunction: (id) => {
return new Promise((resolve, reject) => {
return sequelize.models.myModel.find({
where: {
id: id
}
}).then(mymodel => {
mymodel.a_property++;
resolve(mymodel);
}).catch(err => {
reject(err);
});
});
Why is this happening? I assume it's something to do with Sinon intercepting the resolve thinking it's the result of the find() call, but I'm not certain and I don't know how to fix it.
Is this an anti-pattern? Is there a better way?
As I understand it, mymodel.a_property++ won't change the value in the underlying dataValues object (you'd need to call set to do this). When you call returned_object.get({plain:true}) later it rebuilds the object from the underlying values.
Also, your "classFunction" can be a lot simpler. It doesn't need to wrap its result in an extra Promise:
classFunction: (id) => {
return sequelize.models.myModel.find({
where: { id: id }
}).then(mymodel => {
mymodel.set('a_property', mymodel.a_property + 1);
return mymodel;
});
};