Advanced example of RxJS5 combination with delayed observable - javascript

Hy, i faced a problem with RxJS Combination operators...
here is example object:
const userData = {
dbKeyPath: 'www.example.com/getDbKey',
users:[
{name:'name1'},
{name:'name2'},
{name:'name3'}
]
}
Make observable from them:
const userDataStream = Rx.Observable.of(userData)
const dbKeyStream : string = this.userDataStream.mergeMap(_userData => getDbKey(_userData.dbKeyPath))
const userStream = this.userDataStream.pluck('users').mergeMap(_users=>Rx.Observable.from(_users))
My expected result is stream with combined observables:
[user[0],dbKey],[user[1],dbKey],[user[2],dbKey]...
It works pretty well with withLatestFrom operator:
const result = userStream.withLatestFrom(dbKeyStream) // [user, dbkey]
But, how can i archive same result when i apply .delay() operator to dbKeyStream ?

I would suggest using the mergeMap overload with the selectorFunc:
const userData = {
dbKeyPath: 'www.example.com/getDbKey',
users:[
{name:'name1'},
{name:'name2'},
{name:'name3'}
]
};
function getDbKey(path) {
return Rx.Observable.of('the-db-key:'+path)
.do(() => console.log('fetching db key for path: '+ path))
.delay(1000);
}
const userDataStream = Rx.Observable.of(userData)
.mergeMap(
_userData => getDbKey(_userData.dbKeyPath),
(_userData, dbKey) => _userData.users.map(_usr => ({ user: _usr, dbKey }))
)
.subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>
This gives you the input object and each output value to combine together as you require.

Related

JS/React - How should I use a fetch call inside of a for loop?

I'm still a little unfamiliar with asynchronous functions beyond their simplest forms, so I'm hoping someone here can help me out.
Some information:
getCollection() returns an array of objects that look like this.
{
"id": 1,
"userId": 3,
"gameId": 3498,
"review": "Testing review for game 1"
},
getGameById() takes in a integer (the id of a game) and returns that game object from an external API.
gameArray should be getting filled with game objects whose IDs in the external API match the IDs from the array of objects provided by getCollection.
const [games, setGames] = useState([])
const getGames = () => {
getCollection().then(userGames => {
let gameArray = []
for (const eachObj of userGames) {
if (eachObj.userId === currentUserId) {
getGameById(eachObj.gameId).then(game => {
gameArray.push(game)
})
}
}
Promise.all(gameArray).then(() => {
console.log(gameArray) //logs empty array, but shouldn't be empty
setGames(gameArray)
})
})
}
I have never used Promise.all before, it's just something I saw as a possible solution to this issue I'm having.
Promise.all takes an array of promises.
First you must build the array of promises. Then you must call Promise.all with this array to retrieve all the games :
function getGames() {
getCollection().then(userGames => {
const gamePromises = userGames
.filter(userGame => userGame.userId == currenUserId)
.map(userGame => getGameById(userGame.gameId));
Promise.all(gamePromises).then(games=> {
console.log(games);
setGames(games)
});
})
}
Here is another solution using async function which is maybe more readable
async function getGames() {
const userGames = await getCollection();
const currentUserGames = userGames.filter(({userId}) => userId == currentUserId);
const games = await Promise.all(userGames.map(({gameId}) => getGameById(gameId));
setGames(games);
}
The array that you pass to Promise.all needs to contain the promises, but you're pushing the game objects - and you're doing it asynchronously, so the array is still empty when you pass it to Promise.all.
const getGames = () => {
getCollection().then(userGames => {
let gamePromises = []
for (const eachObj of userGames) {
if (eachObj.userId === currentUserId) {
gamePromises.push(getGameById(eachObj.gameId))
// ^^^^^^^^^^^^^^^^^^ ^
}
}
return Promise.all(gamePromises)
// ^^^^^^ chaining
}).then(gameArray => {
// ^^^^^^^^^
console.log(gameArray)
setGames(gameArray)
})
}
To simplify:
async function getGames() {
const userGames = await getCollection()
const gamePromises = userGames
.filter(eachObj => eachObj.userId === currentUserId)
.map(eachObj => getGameById(eachObj.gameId))
const gameArray = await Promise.all(gamePromises)
console.log(gameArray)
setGames(gameArray)
}

How to optimally combine multiple axios responses

I am working with a React app. I have to create 2 objects using responses from 3 different APIs. For example:
DataObject1 will be created using API1 and API2
DataObject2 will be created using API1, API2, and API3
So, I am thinking about what would be the most optimal way of doing this by making sure 1 call each API only once.
I was thinking this:
const requestOne = axios.get(API1);
const requestTwo = axios.get(API2);
const requestThree = axios.get(API3);
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((...responses) => {
const dataObject1 = createDataObject1(responses[0], responses[1]);
const dataObject2 = createDataObject2(responses[0], responses[1], responses[2]);
// use/access the results
})).catch(errors => {
// react on errors.
})
const createDataObject1 = (response1, response2) => { //Combine required data and return dataObject1 }
const createDataObject2 = (response1, response2, response3) => { //Combine required data and return dataObject2 }
Is there a better way of doing this?
Looks fine.
You can change this
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((...responses) => {
const dataObject1 = createDataObject1(responses[0], responses[1]);
const dataObject2 = createDataObject2(responses[0], responses[1], responses[2]);
// use/access the results
})).catch(errors => {
// react on errors.
})
to
axios.all([requestOne, requestTwo, requestThree]).then((response) => {
const dataObject1 = createDataObject1(responses[0], responses[1]);
const dataObject2 = createDataObject2(responses[0], responses[1], responses[2]);
// use/access the results
}).catch(errors => {
// react on errors.
})
because it is unnecessary to spread and rest.
If you don't want to use them like responses[0], responses[1], etc then you can use:
axios.all([requestOne, requestTwo, requestThree]).then(axios.spread((response1, response2, response3) => {
const dataObject1 = createDataObject1(response1, response2);
const dataObject2 = createDataObject2(response1, response2,response3);
// use/access the results
})).catch(errors => {
// react on errors.
})
Are you using thunk middleware to make async calls in Redux? I don't want to assume that you are, but that seems like a good basic approach here.
const requestOne = axios.get(API1);
const requestTwo = axios.get(API2);
const requestThree = axios.get(API3);
Okay. So now requestOne.data has the result of making the axios get request. Or, would if the thunk creator was async and the code was const requestOne = await axios.get(API1);
Do you need to parse the data further from request___.data ?
If not you can just have
const dataObj1 = { response1: requestOne.data, response2: requestTwo.data }
const dataObj2 = { ... dataObject1, response3: requestThree.data };
Full answer:
// store/yourFile.js code
export const YourThunkCreator = () => async dispatch => {
try {
const const requestOne = await axios.get(API1);
// other axios requests
const dataObj1 = { response1: requestOne.data, response2: requestTwo.data }
const dataObj2 = { ... dataObject1, response3: requestThree.data };
// other code
dispatch(// to Redux Store);
} catch (error) {
console.error(error);
}

Svelte derived stores and array sort

I set up a store containing a list of rides loaded from my API:
const loadRides = () => client.service('rides').find({
query: {
$sort: {
date: -1,
}
}
});
const createRides = () => {
const { subscribe, update } = writable([], async (set) => {
try {
const rides = await loadRides().then((result) => result.data);
set(rides);
} catch (e) {
console.error(e);
}
// Socket update binding?
});
subscribe((rides) => console.debug('rides', rides));
return {
subscribe,
refresh: () => loadRides().then((result) => update(() => result.data)),
};
};
export const rides = createRides();
Then I set a two derived stores for past and future rides:
export const pastRides = derived(
rides,
($rides) => $rides
.filter((ride) => ride.steps.every((step) => step.doneAt))
,
);
export const comingRides = derived(
rides,
($rides) => $rides
.filter((ride) => ride.steps.some((step) => !step.doneAt))
.sort((rideA, rideB) => {
const compare = new Date(rideA.date) - new Date(rideB.date);
console.log(rideA.date, rideB.date, compare);
return compare;
})
,
);
The sort method on the second one does not have any effect.
So I tried to put this method before the filter one. It works, but it also sort $pastRides. In fact, it is sorting the full $rides array and it make sens.
But I does not understand why the sort after filter does not work.
What did I miss?
Array.sort is mutable. Meaning, when you call rides.sort, it will sort and modify rides and return the sorted rides.
When you use derived(rides, ($rides) => ... ), the $rides you received is the original rides array that you call set(rides). So you can imagine that both the pastRides and comingRides received the same $rides array.
you can observe this behavior in this repl
To not having both derived interfere with each other, you can create a new array and sort the new array:
const sorted_1 = derived(array, $a => [...$a].sort());
you can try this out in this repl.

Angular RXJS return array of 3 observable streams

I need to use the following 3 steams to create an array of each. IE: [homePage, mainNavigation, loan_originators]
But this only returns mainNavigation
const homePage = this.flamelinkService.getData('homePage');
const mainNavigation = this.flamelinkService.getNav('mainNavigation');
const loan_originators = this.catalogApiService.get('loan_originators', qry);
return mainNavigation.pipe(
concat( homePage, loan_originators),
first(),
tap( async navResolveData => {
// navResolveData = navResolveData[0];
_log('== Navigation Data Resolver ==> ', 't', navResolveData);
if (isPlatformServer(this.platformId)) {
this.transferState.set(INFO_KEY, navResolveData);
}
}),
);
here im trying forkJoin. but nothing
const homePage = this.flamelinkService.getData('homePage');
const mainNavigation = this.flamelinkService.getNav('mainNavigation');
const loan_originators = this.catalogApiService.get('loan_originators', qry);
return forkJoin([homePage, loan_originators, mainNavigation]).pipe(
first(),
tap( async navResolveData => {
// navResolveData = navResolveData[0];
_log('== Navigation Data Resolver ==> ', 't', navResolveData);
if (isPlatformServer(this.platformId)) {
this.transferState.set(INFO_KEY, navResolveData);
}
}),
);
You can use forkJoin in this case
forkJoin(
[
this.flamelinkService.getData('homePage'),
this.flamelinkService.getNav('mainNavigation'),
this.catalogApiService.get('loan_originators', qry)
]
).subscribe(([res1, res2, res3]) => {
// do something
});

Create an Observable object from a list of Observables

I'm still wrapping my head around RxJS and there is this pattern I keep running into and that I would like to find a more elegant way to write.
Implementing the model part of a Model-View-Intent pattern component, I have a function that takes actions as input an returns a single state$ Observable as output.
function model(actions) {
const firstProperty$ =
const anotherProperty$ = …
// Better way to write this?
const state$ = Rx.Observable.combineLatest(
firstProperty$, anotherProperty$,
(firstProperty, anotherProperty) => ({
firstProperty, anotherProperty
})
);
return state$;
}
So my model method computes a bunch of observables, every one of them emit items that represents a part of the state of my application. That is fine.
But how to I cleanly combine them into a single one observable that emits states, each state being a single object whose keys are the initial observable names?
I borrowed this pattern from https://github.com/cyclejs/todomvc-cycle :
function model(initialState$, actions){
const mod$ = modifications(actions)
return initialState$
.concat(mod$)
.scan( (state, mod) => mod(state))
.share()
}
function modifications(actions){
const firstMod$ = actions.anAction$.map(anAction => (
state => ({ ...state,
firstProperty: anAction.something
})
const secondMod$ = actions.otherAction$.map(otherAction => (
state => ({ ...state,
firstProperty: otherAction.something,
secondProperty: aComputation(otherAction)
})
return Rx.Observable.merge([firstMod$, secondMod$ ]).share()
}
In the main function :
const initialState$ = Rx.Observable.from({})
const actions = intent(DOM)
const state$ = model(initialState$, actions).share()
Using help from CHadrien, here is a working solution.
const prop1$ = Rx.Observable.of('foo');
const prop2$ = Rx.Observable.of('bar');
const prop3$ = Rx.Observable.of('baz');
const prop4$ = Rx.Observable.of('foobar');
function combineObservables(objectOfObservables) {
const keys = Object.keys(objectOfObservables);
const observables = keys.map(key => objectOfObservables[key]);
const combined$ = Rx.Observable.combineLatest(
observables, (...values) => {
var obj = {};
for (let i = 0 ; i < keys.length ; i++) {
obj[keys[i]] = values[i];
}
return obj;
}
);
return combined$;
}
combineObservables({prop1$, prop2$, prop3$, prop4$}).subscribe(x => console.log(x));
And the result:
[object Object] {
prop1$: "foo",
prop2$: "bar",
prop3$: "baz",
prop4$: "foobar"
}

Categories