How to keep the intermediate data with any High-Order operator? - javascript

I'm using the switchMap and mergeMap and need to keep some extra data.
See the code
from(fileList)
.pipe(
mergeMap((file: File) => {
return this.s3Service.checkExists(file);
})
)
.subscribe((hash: any) => {
// I want to access to the `file:File` here as well
console.log(`hash exists`, hash, file);
});
How to achieve this?

Map the result of your s3Service.checkExists method to an object which includes the original file:
from(fileList)
.pipe(
mergeMap((file: File) => {
return this.s3Service.checkExists(file).pipe(map(hash => ({hash, file})));
})
)
.subscribe(data => {
// I want to access to the `file:File` here as well
console.log(`hash exists`, data.hash, data.file);
});

Related

Is there a way to set state for each iteration of a foreach

I'm working with an API within a React Application and I'm trying to make the API calls come back as one promise.
I'm using the Promise.all() method which is working great.
I'm stuck trying to set the results of two API calls to state with their own name. The promise code is working correctly and I am trying to forEach() or map() over the two sets of data and save them to state with their own name.
I'm sure there is a simple solution but I've been scratching my head for far too long over this!
I've tried searching all the docs for .map and .forEach with no luck!
fetchData(){
this.setState({loading: true})
const urls = ['https://api.spacexdata.com/v3/launches/past', 'https://api.spacexdata.com/v3/launches']
let requests = urls.map(url => fetch(url));
Promise.all(requests)
.then(responses => {
return responses
})
.then(responses => Promise.all(responses.map(r => r.json())))
.then(launches => launches.forEach(obj => {
// I need to set both values to state here
}))
.then(() => this.setState({loading: false}))
}
The API call returns two different arrays. I need to set both arrays to State individually with their own name. Is this possible?
If I understand your question correctly, a better approach might be to avoid iteration altogether (ie the use of forEach(), etc). Instead, consider an approach based on "destructuring syntax", seeing you have a known/fixed number of items in the array that is resolved from the prior promise.
You can make use of this syntax in the following way:
/*
The destructing syntax here assigns the first and second element of
the input array to local variables 'responseFromFirstRequest'
and 'responseFromSecondRequest'
*/
.then(([responseFromFirstRequest, responseFromSecondRequest]) => {
// Set different parts of state based on individual responses
// Not suggesting you do this via two calls to setState() but
// am doing so to explicitly illustrate the solution
this.setState({ stateForFirstRequest : responseFromFirstRequest });
this.setState({ stateForSecondRequest : responseFromSecondRequest });
return responses
})
So, integrated into your existing logic it would look like this:
fetchData() {
this.setState({
loading: true
})
const urls = ['https://api.spacexdata.com/v3/launches/past', 'https://api.spacexdata.com/v3/launches']
const requests = urls.map(url => fetch(url));
Promise.all(requests)
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([responseFromFirstRequest, responseFromSecondRequest]) => {
this.setState({ stateForFirstRequest : responseFromFirstRequest });
this.setState({ stateForSecondRequest : responseFromSecondRequest });
return responses
})
.then(() => this.setState({
loading: false
}))
}
If the two arrays won't interfere with each other in the state, is there a problem with just calling setState in each iteration?
.then(launches => launches.forEach(obj => {
this.setState({ [obj.name]: obj });
}))
If you want to minimise the number of updates then you can create an Object from the two arrays and spread that into the state in one call:
.then(launches => this.setState({
...launches.reduce((obj, launch) => {
obj[launch.name] = launch
return obj
}, {})
}))
forEach also provides the index as the second parameter. Wouldn't something like this work?
launches.forEach((obj, idx) => {
if (idx === 0) {
this.setState('first name', obj);
} else if (idx === 1) {
this.setState('second name', obj);
}
})
Also, this portion literally does nothing..
.then(responses => {
return responses
})
and the Promise.all() here also does nothing.
.then(responses => Promise.all(responses.map(r => r.json())))
should be
.then(responses => responses.map(r => r.json()))

Chain React setState callbacks

I need to load three different json files in an ordered sequence and with a fetch (the reason is i'm using nextjs export and i need those files to be read dynamically, so I fetch them when needed and their content can change even after the export)
The first file contains data that is used to create the url for the second file and so on, so each fetch needs an actually updated state to be fetched,
ATM the solution i'm using, since the second and third files are dependent from the first and second respectively, is fetching the first file and setting some state with setState, then in the setState callback fetch the second file and set some other state and so on:
fetch(baseUrl).then(
response => response.json()
).then(
res => {
this.setState({
...
}, () => {
fetch(anotherUrl+dataFromUpdatedState).then(
response => response.json()
).then(
res => {
this.setState({
...
}, () => {
fetch(anotherUrl+dataFromUpdatedState).then(
response => response.json()
).then(
res => {
this.setState({
})
}
)
})
}
).catch(
error => {
//error handling
}
)
})
}
).catch(
error => {
this.setState({ //an error occured, fallback to default
market: defaultMarket,
language: defaultLanguage,
questions: defaultQuestions
})
//this.setLanguage();
}
)
Now: I know that setState must be used carefully as it is async, but as far as I know the callback function is called after state is updated so from that point of view the state should update correctly. Is this solution anti-pattern, bad practice or should be avoided for some reason?
The code actually works, but i'm not sure if this is the way to do it.
You don't need to use the setState callback and read it from the state, since you can just read the data directly from the res object. This way you can make a flat promise chain.
Example
fetch(baseUrl)
.then(response => response.json())
.then(res => {
this.setState({
// ...
});
return fetch(anotherUrl + dataFromRes);
})
.then(response => response.json())
.then(res => {
this.setState({
// ...
});
return fetch(anotherUrl + dataFromRes);
})
.then(response => response.json())
.then(res => {
this.setState({
// ...
});
})
.catch(error => {
this.setState({
market: defaultMarket,
language: defaultLanguage,
questions: defaultQuestions
});
});

RxJs: Executing 3 observables one after another and using results from first in second, and first and second in third requests

I need to be able to execute 3 observables one after another so that I can use result value from 1st one in second and also 1st and 2nd result in the third one.
Something like this (it doesn't work as the serviceId is not visible in the third request):
private setupStuff(): void {
this.initRouteParams().pipe(
switchMap(serviceId => this.getFileInfo(serviceId)),
switchMap(fileName => this.getExistingFile(serviceId, fileName)
.subscribe(response => {
console.log(response);
}))
);
}
You can explicitly return the value of the serviceN to the serviceN+1. Here's the idea :
private setupStuff() {
this.initRouteParams()
.pipe(
switchMap(serviceId => {
return zip(of(serviceId), this.getFileInfo(serviceId))
}),
switchMap(([serviceId, filename]) => {
return zip(of(serviceId), of(filename), this.getExistingFile(serviceId, filename))
})
)
.subscribe(([serviceId, filename, response]) => {
console.log(serviceId, filename, response);
})
}
Edit:
You can fix types errors by explicitly declare types of each input. You probably want to assign the appropriate type for response.
managed to solve it like this:
private checkFilePresence(): void {
const first$: Observable<string> = this.initRouteParams();
const second$: Observable<string> = first$.pipe(
switchMap(config => {
return this.getFileInfo(config);
})
);
const third$: Observable<CkitExistingFileResponse> = combineLatest(first$, second$).pipe(
switchMap(([config, second]) => {
return this.getExistingFile(config, second);
})
);
combineLatest(first$, third$)
.subscribe(() => {
});
}

Calling multiple APIs without too much nesting

I need to call multiple endpoints with each call dependent on the results of the previous call.
return http.get('url1')
.then(response1 => {
return response1.data
})
.then(data => {
http.get('url2' + data)
.then(response2 => {
return response2.data
}) // etc ... until the 'nth url'
})
It can get quite nested. Is there a way to flatten this, maybe using generators?
Promises are made for flattening:
return http.get('url1').then(response1 => {
return response1.data
}).then(data => {
return http.get('url2' + data);
}).then(response2 => {
return http.get('url3' + response2.data);
}) // ...etc
If your JavaScript engine supports async/await, this can be made shorter and more readable within an async function:
async function demo() {
const response1 = await http.get('url1');
const response2 = await http.get('url2' + response1.data);
const response3 = await http.get('url3' + response2.data);
// ...
return responseN;
}
... and then call that:
demo().then(response => {
console.log(response);
// ...etc
});
I don't know that there's a great solution to avoid the string of then(), but ou don't need to nest:
return http.get('url1')
.then(response1 => response1.data)
.then(data => http.get('url2' + data))
.then(response2 => response2.data )
// etc ... until the 'nth url'
If the pattern is the same in every case, you may be able to pass a list of urls and use reduce()
Flatten promise-chains by returning whenever you have a new promise. However, when you have a non-promise value, don't. It just wastes of a micro-task. Just use the value directly instead:
return http.get('url1')
.then(response => http.get('url2' + response.data))
.then(response => doSomethingWith(response.data))
To get a simple data variable name, use destructuring instead:
return http.get('url1')
.then(({data}) => http.get('url2' + data))
.then(({data}) => doSomethingWith(data))

Angular 2 http - filter the observable before returning it

In service I have this defined:
getAllHTTP(): Observable<IUser[]> {
return this.http.get(`${this.url}/users`)
.map(res => res.json())
.catch(err => Observable.throw(err));
}
and in the component:
ngOnInit() {
const self = this;
this._uService.getAllHTTP().subscribe(
function (data) {
console.log(data);
self.listOfUsers = data
},
(error) => console.error(error)
);
}
So now I wanted to filter the data before passing the observable to component and I did this:
getAllHTTP(): Observable<IUser[]> {
return this.http.get(`${this.url}/users`)
.map(res => res.json())
.filter(<IUser>(x) => x.id > 2)
.catch(err => Observable.throw(err));
}
and it doesn't work. The x in filter is an actual array, not an item in the array. This is really odd and I think it has nothing to do with RXjs, because it works like it's suppose to in this example: http://jsbin.com/nimejuduso/1/edit?html,js,console,output
So I did some digging and apparently I should be using flatMap. OK, I did that, replaced map with flatMap but now I get this error:
Cannot find a differ supporting object '[object Object]'
So apparently when I subscribe, I used to get an array and now I get one object at a time... Why is Angular2 behaving like that? Or is this RXjs after all?
You are applying a filter on the observable stream, thus filtering events within the stream and not elements within the array which in this case is a single event in the stream.
What you need to do is:
getAllHTTP(): Observable<IUser[]> {
return this.http.get(`${this.url}/users`)
.map(res => res.json()
.filter(<IUser>(x) => x.id > 2))
.catch(err => Observable.throw(err));
}
The example you linked is working like that because Observable.from is creating a sequence of values for the observable, rather than a singe value being the whole array. This is why the Observable.filter is working as it does, because the array elements are passing by one at a time. See more details in the docs.
It seems like you are asking for a list of user, hence the array. If I got you correctly, you want all users with id > 2:
return this.http.get(`${this.url}/users`)
.map(res => res.json())
.map((users: Array<IUser>) => users.filter(user => user.id > 2)
.catch(err => Observable.throw(err));

Categories

Resources