I'm afraid there are several different problems with your approach.
Some of this has to do with the attempt to merge functional code inside an object-oriented block. This looks odd to my eye:
getCMSMaterials(): Promise<CMSMaterial> {
const response = lastValueFrom(
this.httpService
.get(`${this.apiUrl}/materials`)
.pipe(map((resp) => resp.data)),
);
return response;
}
I would much rather use a pure function such as
const getCMSMaterials = (service, apiUrl) => lastValueFrom (
service .get (`${apiUrl}/materials`) .pipe (map ((resp) => resp .data)),
)
and then call it from a class method if necessary with
async someMethod (args) {
//...
return getCMSMaterial (this .httpService, this .apiUrl)
}
Then you have
const mongoDBMaterials = await this.getMongoDBMaterials();
const CMSMaterial = await this.getCMSMaterials();
and then merging them both with a mergeResponse(cmsList, mongoList)
Are cmsList and CMSMaterial supposed be the same reference?
Next is
private mergeResponse = R.curry((cmsList, mongoList, key = '_id') => {
const omitKeys = R.compose(R.map, R.omit);
const merged = mongoList.map((item, i) => {
const parsed = Object.assign({}, item, cmsList[i]);
return parsed;
});
Assuming that you just skipped the return statement at the end, this function defines but doesn't use the string key and the function omitKeys. And if you're going to be using Ramda, then Object.assign({}, item, cmsList[i]) might as well be written mergeRight (item, cmsList [i])
But the big problem is in the pipeline you create:
const finishedMaterials = R .pipe (
() => this.getMongoDBFinishes(),
(val) => Promise.resolve(val),
R.map((x) => console.log('Y', x)),
(x) => console.log('X', x),
Promise.all.bind(Promise),
// (x) => this.mergeResponse()(getCMSFinishes),
);
First of all, you have a list of functions passed to pipe, and you name the result like it's a data object (finishedMaterials). But pipe always returns a function.
Second, () => this.getMongoDBFinishes() will return a Promise for an array of Materials. That means that following it up with (val) => Promise.resolve(val) is useless. In a better world, this would result in a Promise for a Promise of an array of Materials, but the design of Promises is that this collapses back into the original Promise. It adds nothing.
Next is R.map((x) => console.log('Y', x)). console .log returns an undefined value, so this map from n elements will do some logging and return an array of n undefined values. There is a Ramda function, tap which can be used to add such side-effects without impeding the pipe flow.
After that is another console .log, this one directly in the pipeline, so the next function will simply receive undefined.
Then you call Promise.all.bind(Promise). There's nothing particularly wrong with this, but since you have all sorts of anonymous functions anyway, I think it would be cleaner to write (...xs) => Promise .all (xs), or to extract that into a utility function, perhaps allPromises.
The trouble is that only one value flows down a pipeline. After the first function, which can have multiple inputs, every function can have a single input and a single output, but you want the results of both of your service calls to be passed into here. While there are tricks you can do to make this work, it's something of an abuse of pipelines.
If we were to get all these things fixed, there is still a design problem that would bother me. You are collecting data from two presumably independent sources, and then want to combine them as though they're guaranteed to return arrays of the same length. I would find this quite worrisome.
Onto a solution. Ori Drori already gave a reasonable answer. Here's a somewhat different one:
const getMongoDBMaterials = () => Promise .resolve ([{ id: 1 }, { id: 2 }, { id: 3 }])
const getCMSMaterials = () => Promise .resolve ([{ val: 1 }, { val: 2 }, { val: 3 }])
const getAll = () =>
Promise .all ([getMongoDBMaterials (), getCMSMaterials ()])
.then (apply (zipWith (mergeRight)))
getAll ()
.then (console .log)
.catch (err => console .log (`Error: ${err}`))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {apply, zipWith, mergeRight} = R </script>
Ori Drori's solution is more flexible, allowing you to pass any number of Promises for arrays of objects and it will return you a Promise for an array of merged objects. The clever bit is the use of transpose to flip the array of arrays on its side to allow the use of mergeAll.
Mine is more specific to this problem, but it is easier to call, as it takes no parameters, and it generates those Promises itself. I use zipWith to take the two arrays returned and zip them together into a single array by calling mergeRight on each pair.
Also important to look at if you really want to pipe the result of one Promise's resolved value to another is pipeWith, which together with andThen will let you build the sort of pipeline the title led to to expect. But it's no real help for this problem, which is all about combining the results of multiple concurrent Promises.
Update
A comment asked the following, and my response is too large to respond in a comment:
to fully grasp this in, which ever value is returned in then() which in this case is an array of promises. is/are fed into apply which in turns passes it to zipWith and so on ? or is it more of the opposite way -> right to left ?
I think neither is really the way to think about it.
Although these are not the exact implementations, we can think of those Ramda functions as looking like this:
const apply = (fn) => (xs) => fn (...xs)
const zipWith = (fn) => (xs, ys) => xs .map ((_, i) => fn (xs [i], ys [i])
const mergeRight = (x, y) => Object .assign ({}, x, y)
So apply is a function decorator, which takes a function that accepts a number of arguments, and returns a function that takes those same arguments as an array instead.
zipWith takes a function and returns a function that takes two arrays, zipping them together by applying that function index-by-index.
And mergeRight accepts two objects and performs a shallow merge on them, returning a new object.
This means that zipWith (mergeRight) takes two arrays, and zips them together by performing an index-by-index shallow merge.
And apply (zipWith (mergeRight)) alters that so that instead of two separate input arrays, it takes an array holding two arrays that will will zip with a shallow merge.
We will call it inside .then() because the Promise .all call returns a Promise that will resolve with an array containing the array results of your two calls.
This is roughly equivalent:
.then (([xs, ys]) => xs .map ((_, i) => Object .assign ({}, xs [i], ys [i])))
The thought is that once you understand the functions, this is significantly more readable:
.then (apply (zipWith (mergeRight)))
and we can see a chain of equivalent expressions by substitutions:
/* 1: */ .then (([xs, ys]) => xs .map ((_, i) => Object .assign ({}, xs [i], ys [i])))
/* 2: */ .then (([xs, ys]) => xs .map ((_, i) => mergeRight (xs [i], ys [i])))
/* 3: */ .then (([xs, ys]) => zipWith (mergeRight) (xs, ys))
/* 4: */ .then (apply (zipWith (mergeRight)))
I hope that clears it up a bit.