Understanding merge in rxjs6 (and redux-observable specifically) - merge

Context: I'm trying to use redux-observable with rxjs v6 to trigger multiple actions after fetching a resource.
Problem: I can trigger a single action after fetching the resource no problem; but my attempt to trigger multiple actions has me stumped. Specifically, my attempt shown below to use rxjs merge to trigger multiple actions seems so standard and close to documented examples (see e.g. here) that I can't understand where I'm going wrong. Here is a breakdown of my code with typescript types indicated in comments; I've simplified things for the sake of problem clarity:
import { from } from "rxjs";
import { merge, mergeMap, map } from "rxjs/operators";
import { AnyAction } from "redux";
... //Setup related to rxjs-observable epics
function myEpic(
action$: Observable<AnyAction>,
state$: any,
{fetchPromisedStrings}: any
) {
return action$.pipe(
ofType('MY_ACTION'),
mergeMap(
action =>
{
const demoAction1: AnyAction = {type:'FOO1', payload:'BAR1'};
const demoAction2: AnyAction = {type:'FOO2', payload:'BAR2'};
const w = from(fetchPromisedStrings()) //const w: Observable<string[]>
const x = w.pipe( map(() => demoAction1)); //const x: Observable<AnyAction>
const y = w.pipe( map(() => demoAction2)); //const y: Observable<AnyAction>
//My attempt to merge observables:
const z = merge(x,y); // const z: OperatorFunction<{}, {} | AnyAction>
// return x; // Works :)
// return y; // Works :)
return z; // Doesn't work :(
}
)
);
}
This code gives me the following error when I try to return z:
TypeError: You provided 'function (source) { return source.lift.call(_observable_merge__WEBPACK_IMPORTED_MODULE_0__["merge"].apply(void 0, [source].concat(observables))); }' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable
So what's the problem here? I'd expect merge in the above code to take the two Observable<AnyAction> types and return a single Observable<AnyAction> type (or some more general type that's compatible with Observable<AnyAction>, which my epic needs in order to function correctly). Instead, I get some opaque type called OperatorFunction<{}, {} | AnyAction> that, evidently, isn't compatible with Observable<AnyAction>. Could someone please unmuddle my thinking here? Is merge not the operator I am supposed to use here, or is my entire rxjs-v6 pattern wrong for the (seemingly) simple goal of triggering multiple actions after fetching a promised resource?

Your code is ok, but you've imported merge operator instead of merge observable factory
Just do:
import { merge } from 'rxjs';

Related

Filtering a collection of IO's: List[IO[Page]] scala

I am refactoring a scala http4s application to remove some pesky side effects causing my app to block. I'm replacing .unsafeRunSync with cats.effect.IO. The problem is as follows:
I have 2 lists: alreadyAccessible: IO[List[Page]] and pages: List[Page]
I need to filter out the pages that are not contained in alreadyAccessible.
Then map over the resulting list to "grant Access" in the database to these pages. (e.g. call another method that hits the database and returns an IO[Page].
val addable: List[Page] = pages.filter(p => !alreadyAccessible.contains(p))
val added: List[Page] = addable.map((p: Page) => {
pageModel.grantAccess(roleLst.head.id, p.id) match {
case Right(p) => p
}
})
This is close to what I want; However, it does not work because filter requires a function that returns a Boolean but alreadyAccessible is of type IO[List[Page]] which precludes you from removing anything from the IO monad. I understand you can't remove data from the IO so maybe transform it:
val added: List[IO[Page]] = for(page <- pages) {
val granted = alreadyAccessible.flatMap((aa: List[Page]) => {
if (!aa.contains(page))
pageModel.grantAccess(roleLst.head.id, page.id) match { case Right(p) => p }
else null
})
} yield granted
this unfortunately does not work with the following error:
Error:(62, 7) ';' expected but 'yield' found.
} yield granted
I think because I am somehow mistreating the for comprehension syntax, I just don't understand why I cannot do what I'm doing.
I know there must be a straight forward solution to such a problem, so any input or advice is greatly appreciates. Thank you for your time in reading this!
granted is going to be an IO[List[Page]]. There's no particular point in having IO inside anything else unless you truly are going to treat the actions like values and reorder them/filter them etc.
val granted: IO[List[Page]] = for {
How do you compute it? Well, the first step is to execute alreadyAccessible to get the actual list. In fact, alreadyAccessible is misnamed. It is not the list of accessible pages; it is an action that gets the list of accessible pages. I would recommend you rename it getAlreadyAccessible.
alreadyAccessible <- getAlreadyAccessible
Then you filter pages with it
val required = pages.filterNot(alreadyAccessible.contains)
Now, I cannot decipher what you're doing to these pages. I'm just going to assume you have some kind of function grantAccess: Page => IO[Page]. If you map this function over required, you will get a List[IO[Page]], which is not desirable. Instead, we should traverse with grantAccess, which will produce a IO[List[Page]] that executes each IO[Page] and then assembles all the results into a List[Page].
granted <- required.traverse(grantAccess)
And we're done
} yield granted

Babel: replaceWithSourceString giving Unexpected token (1:1)

I am trying to replace dynamically "import" statements.
Here is an example that checks if the import ends with a Plus.
module.exports = function(babel) {
return {
visitor: {
ImportDeclaration: function(path, state) {
// import abc from "./logic/+"
if( ! path.node.source.value.endsWith("/+"))
return;
path.replaceWithSourceString('import all from "./logic/all"')
}
}
}
}
This gives an error of
SyntaxError: src/boom.js: Unexpected token (1:1) - make sure this is an expression.
> 1 | (import all from "./logic/all")
The problem is that replaceWithSourceString is wrapping the string in rounded braces.
If I change the replaceWithSourceString to
path.replaceWithSourceString('console.log("Hi")')
and this works.. ¯_(ツ)_/¯
Any and all help you be great
replaceWithSourceString should really be avoided, because it is just not a very good API, as you are seeing. The recommended approach for creating ASTs to insert into the script is to use template. Assuming this is for Babel 7.x, you can do
const importNode = babel.template.statement.ast`import all from "./logic/all"`;
path.replaceWith(importNode);

I found this example of a function that accepts a callback but it's not working?

This code is similar to our previous mean() function, except in the
following if block where we check to see if a callback has been
provided. If it has, then the callback is applied to each value
before being added to the total; otherwise, the total is calculated
using just the values from the array given as the first argument
mean([2,5,7,11,4]); // this should just calculate the mean << 5.8
mean([2,5,7,11,4],x => 2*x); << 11.6
function mean(array ,callback) {
if (callback) {
array.map( callback );
}
const total = array.reduce((a, b) => a + b);
return total/array.length;
}
console.log(mean([2,5,7,11,4,5],x => 2*x));
function mean(array,callback) {
if (callback) {
array = array.map( callback ); // <--- note changes here
}
const total = array.reduce((a, b) => a + b);
return total/array.length;
}
console.log(mean([2,5,7,11,4])); //5.8 fine
console.log(mean([2,5,7,11,4],x => 2*x)) // 11.6
You weren't off by much. Check out the exact definition of Array.prototype.map() The return value from that function is a new array with each element being the result of the callback function.
I will say, your question got me to review Array.prototype.map() and passing callback functions. Thanks!
Almost forgot.. See how simple my code is formatted? That makes it easy for someone to see what's going on quickly. Recommend you do the same when posting questions here in the future.

How to combine the elements of an arbitrary number of dependent Fluxes?

In the non reactive world the following code snippet is nothing special:
interface Enhancer {
Result enhance(Result result);
}
Result result = Result.empty();
result = fooEnhancer.enhance(result);
result = barEnhancer.enhance(result);
result = bazEnhancer.enhance(result);
There are three different Enhancer implementations taking a Result instance, enhancing it and returning the enhanced result. Let's assume the order of the enhancer calls matters.
Now what if these methods are replaced by reactive variants returning a Flux<Result>? Because the methods depend on the result(s) of the preceding method, we cannot use combineLatest here.
A possible solution could be:
Flux.just(Result.empty())
.switchMap(result -> first(result)
.switchMap(result -> second(result)
.switchMap(result -> third(result))))
.subscribe(result -> doSomethingWith(result));
Note that the switchMap calls are nested. As we are only interested in the final result, we let switchMap switch to the next flux as soon as new events are emitted in preceding fluxes.
Now let's try to do it with a dynamic number of fluxes. Non reactive (without fluxes), this would again be nothing special:
List<Enhancer> enhancers = <ordered list of different Enhancer impls>;
Result result = Result.empty();
for (Enhancer enhancer : enhancers) {
result = enhancer.enhance(result);
}
But how can I generalize the above reactive example with three fluxes to deal with an arbitrary number of fluxes?
I found a solution using recursion:
#FunctionalInterface
interface FluxProvider {
Flux<Result> get(Result result);
}
// recursive method creating the final Flux
private Flux<Result> cascadingSwitchMap(Result input, List<FluxProvider> fluxProviders, int idx) {
if (idx < fluxProviders.size()) {
return fluxProviders.get(idx).get(input).switchMap(result -> cascadingSwitchMap(result, fluxProviders, idx + 1));
}
return Flux.just(input);
}
// code using the recursive method
List<FluxProvider> fluxProviders = new ArrayList<>();
fluxProviders.add(fooEnhancer::enhance);
fluxProviders.add(barEnhancer::enhance);
fluxProviders.add(bazEnhancer::enhance);
cascadingSwitchMap(Result.empty(), fluxProviders, 0)
.subscribe(result -> doSomethingWith(result));
But maybe there is a more elegant solution using an operator/feature of project-reactor. Does anybody know such a feature? In fact, the requirement doesn't seem to be such an unusual one, is it?
switchMap feels inappropriate here. If you have a List<Enhancer> by the time the Flux pipeline is declared, why not apply a logic close to what you had in imperative style:
List<Enhancer> enhancers = <ordered list of different Enhancer impls>;
Mono<Result> resultMono = Mono.just(Result.empty)
for (Enhancer enhancer : enhancers) {
resultMono = resultMono.map(enhancer::enhance); //previousValue -> enhancer.enhance(previousValue)
}
return resultMono;
That can even be performed later at subscription time for even more dynamic resolution of the enhancers by wrapping the whole code above in a Mono.defer(() -> {...}) block.

Should 'require' go inside or outside of the Future?

How do I replace my first conditional with the require function in the context of a Future? Should I wrap the entire inRange method in a Future, and if I do that, how do I handle the last Future so that it doesn't return a Future[Future[List[UserId]], or is there a better way?
I have a block of code that looks something like this:
class RetrieveHomeownersDefault(depA: DependencyA, depB: DependencyB) extends RetrieveHomeowners {
def inRange(range: GpsRange): Future[List[UserId]] = {
// I would like to replace this conditional with `require(count >= 0, "The offset…`
if (count < 0) {
Future.failed(new IllegalArgumentException("The offset must be a positive integer.")
} else {
val retrieveUsers: Future[List[UserId]] = depA.inRange(range)
for (
userIds <- retrieveUsers
homes <- depB.homesForUsers(userIds)
) yield FilterUsers.withoutHomes(userIds, homes)
}
}
}
I started using the require function in other areas of my code, but when I tried to use it in the context of Futures I ran into some hiccups.
class RetrieveHomeownersDefault(depA: DependencyA, depB: DependencyB) extends RetrieveHomeowners {
// Wrapped the entire method with Future, but is that the correct approach?
def inRange(range: GpsRange): Future[List[UserId]] = Future {
require(count >= 0, "The offset must be a positive integer.")
val retrieveUsers: Future[List[UserId]] = depA.inRange(range)
// Now I get Future[Future[List[UserId]]] error in the compiler.
for (
userIds <- retrieveUsers
homes <- depB.homesForUsers(userIds)
) yield FilterUsers.withoutHomes(userIds, homes)
}
}
Any tips, feedback, or suggestions would be greatly appreciated. I'm just getting started with Futures and still having a tough time wrapping my head around many concepts.
Thanks a bunch!
Just remove the outer Future {...} wrapper. It's not necessary. There's no good reason for the require call to go inside the Future. It's actually better outside since then it will report immediately (in the same thread) to the caller that the argument is invalid.
By the way, the original code is wrong too. The Future.failed(...) is created but not returned. So essentially it didn't do anything.