I have an object that might or might not exist. I'm using Observable.if to determine what action to do based on its existence.
However, the else part of Observable.if seems to be running even when the object is undefined. I get this error: TypeError: Cannot read property 'genesisHash' of undefined
console.log("jjjjjjjjjjjjjjjj: ", t); // prints out jjjjjjjjjjjjjjjj: undefined
return Observable.if(
() => !t,
Observable.of(nodeActions.requestGenesisHashes()),
Observable.of(
nodeActions.treasureHunt({
genesisHash: t.genesisHash // error occurs here
})
)
);
How would I delay the call to nodeActions.treasureHunt so that it doesn't try looking at the genesisHash attribute on t?
I'm using this through redux-observable by the way. Hence using actions.
Your then/else observable creation is not wrapped in a function, so the else observable setup code is ran when passing to the Observable.of(...) function. You probably need to just use regular if/else type of logic:
const actions = !t ? nodeActions.requestGenesisHashes() :
nodeActions.treasureHunt({ genesisHash: t.genesisHash });
return Observable.of(actions);
If you really wanted to use that method, you could just create the Observable manually:
return Observable.if(
() => !t,
Observable.of(nodeActions.requestGenesisHashes()),
Observable.create(obs => {
obs.next(nodeActions.treasureHunt({ genesisHash: t.genesisHash }));
obs.complete();
});
);
That should delay the use of the t variable until something tries to subscribe to the observable, which will never happen.
Related
I'm tring to learn rxjs but i'm having some problems with the map operator. Here's my code:
const courses$ = http$.pipe(map(res => {res['payload']}))
courses$.subscribe(
(datas) => {console.log(datas)},
noop,
() => {console.log('finished')}
)
createObservable() is a function that i defined. It simply makes an http request to a db and return the datas in json format. I tested it out and works.
Debbuging I saw that console logging res['payload'] directly in map works, but when i console log it in the observable subscribe return undefined. Here there is a picture of the structure of the dats that i recive from the db. Thank u all
you're not actually returning in your map, change to:
const courses$ = http$.pipe(map(res => res['payload']))
adding {} around the function requires a return statement
This is somewhat related to a previous question I asked. The feature$ function in that question returns an observable with a map that uses the parameter passed to the function:
feature$ = (feature: string): Observable<FeatureConfig | null> => {
return this.features$.pipe(
map((features: FeatureConfig[]) => {
return (
features.find((featureConfig: FeatureConfig) => {
return featureConfig.key === feature;
})?.value ?? null
);
})
);
};
This is then used like this elsewhere:
this.featureService
.feature$("featureName")
.subscribe((featureConfig: FeatureConfig) => {
...
});
Or:
someFeature$ = this.featureService.feature$("featureName");
The features$ observable is (I think, by definition) a hot observable as its value can change throughout the life of the observable and it never completes. While this seems to work for its intended purpose, I am just wondering what the effect this has when there are many subscribers to that feature$ function. I fear there might be some unintended behavior that I am not immediately noticing.
Is this a bad pattern in general? And if so, is there a better pattern to do something similar? That is, subscribe to an observable created with a parameter passed to a function.
For example, would something like this be preferred?
feature$ = (featureName: string): Observable<FeatureConfig | null> => {
return of(featureName).pipe(
mergeMap((feature: string) => combineLatest([of(feature), this.features$])),
map(([feature, features]: [string, FeatureConfig[]]) => {
return (
features.find((featureConfig: FeatureConfig) => {
return featureConfig.key === feature;
})?.value ?? null
);
})
);
};
Or does it matter?
The the second stream example is a bit overly complicated, your features$$ is a Behavior subject that might continuously updating itself. Your intend is only take in parameter and process through the features array and output the found feature, the first form of the code is more appropriate.
As the source stream is a BehaviorSubject you will always have a value once subscribe(), just don't forget to unsubcribe() to prevent memory leak. Alternatively use take(1) or first() operator before subscribe()
When you create an observable from a function you get a new instance of that stream, it is a hot observable but not shared(), so filtering on 'featureA' wouldn't affect result on filtering on 'featureB', and yes of() and combineLatest() really does nothing in your use case, as those are static and unchange function param
I often use the promisify method, which converts a method with a callback signature (e.g. fucntion fn(cb) { ... }) to a method which returns a Promise. It can make the source code a lot cleaner and more compact. So far so good.
Slightly different, are methods which have a callback method, but the callback is called multiple times. In that case a Promise cannot do the trick, because a promise can only be executed once.
In theory, those methods could return a Subject. (e.g. a BehaviorSubject) which would then be fired multiple times.
This made me wondering:
Is there a subjectify method somewhere, which can do this for me ?
For example: it could be useful when there is a method which parses a big document. To report on its progress, it could use a callback method. But wrapping it in a Subject could be more convenient.
There are some operator that helps you convert callback to observable
https://rxjs-dev.firebaseapp.com/api/index/function/fromEventPattern
but you can quite easily integrate subject to callback like below
const progress=new Subject()
myEvent.on('progress', (p)=>{
progress.next(p)
})
You may want to consider to create an Observable in cases your callback is called repeatedly and you want to notify a stream of events as a result of the callback being called.
Let's consider, as an example, the node readline function, which accepts a callback which gets fired for each line read and a second callback which is called when the file end is reached.
In this case, we can create an Observable which emits for each line read and completes when the end is reached, like in the following example
function readLineObs(filePath: string) => {
return new Observable(
(observer: Observer<string>): TeardownLogic => {
const rl = readline.createInterface({
input: fs.createReadStream(filePath),
crlfDelay: Infinity,
});
rl.on('line', (line: string) => {
observer.next(line);
});
rl.on('close', () => {
observer.complete();
});
},
);
};
I have a function 'open' that I want to return a promise to the calling method, so that in my close function I resolve the Promise
open(config){
this.closePromise = new Promise()
return this.closePromise
}
close(closeArgs){
this.closePromise.resolve(closeArgs)
...
}
... so that my calling method gets a callback when my 'close' function is called like this :
myService.open(myData)
.then( closeArgs => console.log('closed'))
I am getting errors like 'Promise resolver undefined is not a function'
I am looking at the docs here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve but nothing seems to fit my needs. ( Im starting to wonder if I should just use an Observable in this scenario, as it may fit my situation better ).
Is this a bad scenario to be using a Promise or am I just doing it wrong ?
Thanks
A promise does not have a resolve method. You need to pass an executor callback to the new Promise constructor to gain access to the resolving function:
open(config) {
return new Promise(resolve => {
if (this.onClose) throw new Error("not yet closed, cannot open (again)");
this.onClose = resolve;
});
}
close(closeArgs) {
if (!this.onClose) throw new Error("not yet opened, cannot close (again)")
this.onClose(closeArgs);
this.onClose = null;
}
I have an action that will then trigger an ajax request.
If the action fails for some reason, I want to do nothing. Instead of creating a blank action that just returns the previous state, is there a no-op function I can execute?
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
)
.catch(error => Observable.noop())
};
I already have the meetups saved from the last time the app was open (using redux-persist), so if the api request fails I just want it to do nothing.
Is this possible?
I found this from Rxjs but I have no clue how to use it: https://xgrommx.github.io/rx-book/content/helpers/noop.html
Heads up: that link to xgrommx references RxJS v4, not v5 or v6. noop is also just a function that does nothing--not an Observable which emits nothing, which is what I believe you're looking for.
That said, I would highly discourage against swallowing errors completely like this. It can make debugging this and other things very very hard later. I would at least log the error message out.
v5 comes with Observable.empty() or import { empty } from 'rxjs/observable/empty'; which produces an Observable that will emit nothing and just immediately complete.
However, there are some other subtleties you probably will run into next. If you let the ajax error propagate up to the outer operator chain, outside of the mergeMap, your Epic will not longer be listening for future actions! Instead, you'll want to catch errors as early as possible, in this case by placing the catch inside the mergeMap. We often call this "isolating our observer chains"
export default function fetchMeetups(action$) {
return action$.ofType(statusActions.START_APP)
.mergeMap(action =>
ajax.getJSON(`${config.API_BASE_URL}/api/v1/meetups`)
.map(meetups => calendarActions.meetupsReceived(meetups))
.catch(e => {
console.error(e);
return Observable.empty();
})
);
};
Now, whenever the ajax (or the map operation) errors, we're catching that error before it propagates out and instead switching to our empty Observable which will complete immediately so the inner chain is now "done" but our Epic will continue to listen for future actions.
UPDATE:
In v6 empty() is imported from the root import { empty } from 'rxjs'; or it is also available as a singleton import { EMPTY } from 'rxjs';, which can be used as-is, you don't call it like you would empty(). It can be reused because Observables are lazy and act like a factory anyway so empty() was redundant.
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
// etc
source$.pipe(
catchError(e => {
console.error(e);
return EMPTY; // it's not a function, use it as-is.
})
);
As rxjs also accepts arrays, you can simple provide an empty array when you don't want to emit anything
...
.catch(error => return [];)