I just want to make sure that code inside map function should be called only on success but not on failure.
delete(department: Department): Observable<Department[]> {
return this.post('/delete', body).map(response => {
let index: number = this.departments.indexOf(department);
if (index > -1) {
this.departments.splice(index, 1);
}
return this.departments;
});
}
I don't know whether the code inside map executes only on success or not. And also, I have to return an Observable so , I can't apply subscribe function here.
Observable#map operator will be executed on success response only (e.g. status 200). Observable#catch operator is intended to catch failures.
Also, Observable#catch operator will catch javascript errors thrown while mapping success response as well. Example:
fetchDashboardData(): Observable<Dashboard> {
return this._http.get(reqUrl, reqOptions)
.map((response: Response) => new Dashboard(response.json().items[0].dashboard))
.catch((error: any) => {
if (error instanceof Error) {
// js error, e.g. response.json().items was an empty array
console.log(error); // => “Cannot read property 'dashboard' of undefined...
return Observable.throw('Incomplete response data!');
} else {
return Observable.throw('Server error!');
}
});
}
Subscribe block:
this.dashboardService.fetchDashboardData().subscribe(
(dashboard: Dashboard) => {
this.dashboard = dashboard;
console.log('Success fetching dashboard data!', dashboard);
},
(errMssg: string) => {
console.error(errMssg); // => 'Incomplete response data!'
// or 'Server error!'
},
() => {
// finally block!
}
);
Related
I am encountering a hard time to solve these 2 issues and still working on it. Hopefully someone can help me. Thanks in advance!
1.) I have a map.service with this function.
readKML(file): any {
const exportUrl = URL.createObjectURL(file);
const reader = new HERE_MAP.data.kml.Reader(exportUrl);
// Parse the document
reader.parse();
reader.addEventListener('statechange', (evt) => {
if (evt.state === HERE_MAP.data.AbstractReader.State.READY) {
return of({
markers: ['test'],
lines: ['test]
});
}
if (evt.state === HERE_MAP.data.AbstractReader.State.ERROR) {
this.message.error('KML parsing error');
return of(null);
}
});
}
This function is being called in the component and expect to get the return data.
this.map.service.readKML(file)
.subscribe(
data => {
console.log(data);
}
)
The problem is im getting an error
this.map.readKML(...).subscribe is not a function
Expectation:
The data should received if the stateChange is equal to 'READY'.
2.) The service will be called at the same time with different param value and expected to have different returns if number 1 is okay.
However, this can be solved if 1 is still be an issue.
this.map.service.readKML(file1)
.subscribe(
data => {
console.log('one', data);
}
)
this.map.service.readKML(file1)
.subscribe(
data => {
console.log('two', data);
}
)
If you guys have an idea on how to fix this or other approach much appreciated. Thanks!
Issue: First why your code failed:
addEventListener is an async operation and is returning void because of is not even reached by the time your function has already returned from readKML.
readKML(file): any {
const exportUrl = URL.createObjectURL(file);
const reader = new HERE_MAP.data.kml.Reader(exportUrl);
// Parse the document
reader.parse();
// 'addEventListener' is an async operation which returns void, so you are returning a void
reader.addEventListener('statechange', (evt) => {
if (evt.state === HERE_MAP.data.AbstractReader.State.READY) {
return of({ // reached here after already returning void
markers: ['test'],
lines: ['test']
});
}
if (evt.state === HERE_MAP.data.AbstractReader.State.ERROR) {
this.message.error('KML parsing error');
return of(null);
}
});
}
Fix: you need to return an observable. So wrap the addEventListener into an observable. Complete the observable once the response comes.
Since you are returning observable, every time that you call this readKML method a new observable would be created.
readKML(file): any {
const exportUrl = URL.createObjectURL(file);
const reader = new HERE_MAP.data.kml.Reader(exportUrl);
reader.parse();
return new Observable((subscriber) => { // return observable
reader.addEventListener('statechange', (evt) => {
if (evt.state === HERE_MAP.data.AbstractReader.State.READY) {
subscriber.next({
markers: ['test'],
lines: ['test']
}); // return response
subscriber.complete(); // always complete for next so only single reponse goes for single method call (cold observable)
}
if (evt.state === HERE_MAP.data.AbstractReader.State.ERROR) {
this.message.error('KML parsing error');
subscriber.next(null); // you can also throw error ` throw throwError(response); `;
subscriber.complete();
}
});
});
}
I have an axios interceptor
let authTokenRequest = null
axios.interceptors.response.use(undefined, err => {
const error = err.response
if (error.status === 401 && error.config && !error.config.__isRetryRequest) {
return getAuthToken()
.then(response => {
saveTokens(response.data)
error.config.__isRetryRequest = true
return axios(error.config)
})
.catch(e => {
throw e
})
.finally(() => {
authTokenRequest = null
})
}
})
This calls the following function
function getAuthToken() {
if (!authTokenRequest) {
authTokenRequest = RefreshToken();
}
return authTokenRequest;
}
So I'd expect the RefreshToken function to only be called once.
But it's called twice in quick succession, even tho authTokenRequest is quickly assigned the promise inside the if statement making it truthy - so a second call should not be possible?
This behaves correctly in the browser but not in NodeJS, any reason why?
Good day for all,
I am doing a React course and I'd submited the code to the reviewer. He's returned me few comments and there is one comment I'm not being able to solve.
The comment is the following:
Check if (query === this.state.query) to ensure you are not going to replace the contents to an old response
And part of the code is the one below:
updateQuery = (query) => {
this.setState({
query: query
})
this.updateWantedBooks(query);
}
updateWantedBooks = (query) => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
if (wantedBooks.error) {
this.setState({ wantedBooks: [] });
} else {
this.setState({ wantedBooks: wantedBooks });
}
})
} else {
this.setState({ wantedBooks: [] });
}
}
Anyone could help me what do am I suppose to do?
Regards.
Code reviewer is right, you don't really want to replace the response if user has entered the very same query.
You have to store somewhere what for user has searched recently:
this.setState({ wantedBooks: [], query });
In case of success response:
this.setState({ wantedBooks, query });
And then check it in case of further searches:
if (query && query !== this.state.query) {
// continue the search only if query is different that current
Instead of relying on an outer member which is open to abuse by other code, you can employ a factory function to more safely trap a member.
As you have discovered, trapping and testing query == this.state.query can be made to work but is arguably not the best solution available.
With a little thought, you can force each call of updateWantedBooks() automatically to reject the previous promise returned by the same function (if it has not already settled), such that any success callbacks chained to the previous promise don't fire its error path is taken.
This can be achieved with a reusable canceller utility that accepts two callbacks and exploits Promise.race(), as follows:
// reusable cancellation factory utility
function canceller(work, successCallback) {
var cancel;
return async function(...args) {
if (cancel) {
cancel(new Error('cancelled')); // cancel previous
}
return Promise.race([
work(...args),
new Promise((_, reject) => { cancel = reject }) // rejectable promise
]).then(successCallback);
};
};
Here's a demo ...
// reusable cancellation factory utility
function canceller(work, successCallback) {
var cancel;
return async function(...args) {
if (cancel) {
cancel(new Error('cancelled')); // cancel previous
}
return Promise.race([
work(...args),
new Promise((_, reject) => { cancel = reject })
]).then(successCallback);
};
};
// delay utility representing an asynchronous process
function delay(ms, val) {
return new Promise(resolve => {
setTimeout(resolve, ms, val);
});
};
function MySpace() {
// establish a canceller method with two callbacks
this.updateWantedBooks = canceller(
// work callback
async (query) => delay(500, query || { 'error': true }), // a contrived piece of work standing in for BooksAPI.search()
// success callback
(wantedBooks => this.setState(wantedBooks)) // this will execute only if work() wins the race against cancellation
);
this.setState = function(val) {
console.log('setState', val);
return val;
};
};
var mySpace = new MySpace();
mySpace.updateWantedBooks({'value':'XXX'}).then(result1 => { console.log('result', result1) }).catch(error => { console.log(error.message) }); // 'cancelled'
mySpace.updateWantedBooks(null).then(result2 => { console.log('result', result2) }).catch(error => { console.log(error.message) }); // 'cancelled'
mySpace.updateWantedBooks({'value':'ZZZ'}).then(result3 => { console.log('result', result3) }).catch(error => { console.log(error.message) }); // {'value':'ZZZ'} (unless something unexpected happened)
Note that canceller() doesn't attempt to abort the asynchronous process it initiates, rather it stymies the success path of the returned promise in favour of the error path.
I think reviewer's point is that response of Search API is asynchronous and result for "query 1" can arrive after user changed his mind and already requested search "query 2". So when response arrive - we need to check if we really interested in it:
updateQuery = query => {
this.setState({
query: query
wantedBooks: []
})
this.updateWantedBooks(query);
}
updateWantedBooks = query => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
// if updateQuery("query1) and updateQuery("query2") called in a row
// then response for query1 can arrive after we requested query2
// => for some period of time we'll show incorrect search results
// so adding check if query still the same can help
if (query !== this.state.query) {
// outdated response
return;
} else if (wantedBooks.error) {
// query is okay, but server error in response
this.setState({
wantedBooks: []
})
} else {
// success response to requested query
this.setState({ wantedBooks });
}
})
}
}
Guys I´ve done some tests with your answers, but I realize that somehow the code was behavioring strangely.
So, I've seen in other part of the reviewer comments, a part which I hadn't had seen before do my answer here, the following comment:
Inside 'then' part of the promise check if(query === this.state.query) to ensure you are not going to replace the contents to an old response.
And this "Inside 'then'" has been beating in my brain.
So, I think I've arrived in a satisfatory code; sure, maybe it isn't the definite solution, that's why I want to show here for you and feel free to comment if I'd have to make some improvement. Here below I put the code:
updateQuery = (query) => {
this.setState({
query: query
})
this.updateWantedBooks(query);
}
updateWantedBooks = (query) => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
if (wantedBooks.error) {
this.setState({ wantedBooks: [] });
} else if (query !== this.state.query) {
this.setState( { wantedBooks: [] });
} else {
this.setState({ wantedBooks: wantedBooks });
}
})
} else {
this.setState({ wantedBooks: [] });
}
}
Regards
What am I misunderstanding about how to use the .subscribe function in combination with the .do function?
Here is my observable sequence:
lookupSubscriber = (text$: Observable<string>) =>
text$.debounceTime(300)
.distinctUntilChanged()
.do(() => this.searching = true)
.switchMap(term => {
var data = this._callApi(this.lookupSubscriberAPI, term)
.do(() => {
this.searchFailed = false;
this.searching = false;
})
.catch(() => {
this.searchFailed = true;
this.searching = false;
return Observable.of([]);
})
return data;
})
.do(() => this.searching = false);
If my _callApi function is as follows, it works:
_callApi(url: string, term: string) {
if (term === '') {
return of.call([]);
}
return map.call(this.dataService.get(url + term), response => {
var data = this._transformSubscriberInfo(response);
return data;
})
}
However, when I try to rewrite it with a subscribe function like this:
_callApi = (url: string, term: string) => {
return this.dataService.get(url + term)
.subscribe(
response => { this._transformSubscriberInfo(response) },
error => error.text(),
() => {
if (Logging.isEnabled.light) {
console.log('%c API Call Complete', Logging.normal.orange);
}
))
}
... then the data call succeeds, but I receive error: Property 'do' does not exist on type 'Subscription'.
Basically I am trying to catch errors and run an "always" function after the api call, as shown in the second version of _callApi.
The first version of _callApi seems to return an Observable while the second one returns a Subscription object. And Subscription does not expose do, exactly as the error message states.
What you may want to try is to use a version of do that accepts error and complete callbacks in addition to the next callback:
return this.dataService.get(url + term)
.map(response => this._transformSubscriberInfo(response))
.do(
response => { /* log response */ },
error => { /* log error */ },
() => { /* log completion */ }
);
It worth mentioning that do is not capable of transforming the source stream, the observable it returns contains the same values as the observable it is called on. That's why we need the line .map(response => this._transformSubscriberInfo(response)).
Also complete callback should not be confused for an "always" function: it gets called only when the source observable completes and it does not get called when the observable produces an error or gets unsubscribed.
I'm using Ionic 3, Firebase, and FirebaseAuth. I'm trying to use the currently authenticated user to retrieve a list of objects for this user. Below is my attempt and I'm getting an error
getContacts Error: TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
I'm missing something here as this seems like it should update the onNext with each new piece of data.
My attempt:
getContacts() {
this.contactList.length = 0;
this.data.getAuthenticatedUserContactList().subscribe(
(contact => {
this.contactList.push(contact);
}),
(error => console.error(`getContacts Error: ${error}`))
);
}
getAuthenticatedUserContactList() {
return this.afAuth.getAuthUser()
.flatMap(user => this.database.list(`contact-lists/${user.uid}`).take(1))
.flatMap((contactList) => {
if (contactList && contactList.length > 0) {
contactList.forEach(element => {
console.log(`ForEach: ${element.userId}`); // THIS LOGS THE IDS AS EXPECTED
return this.database.object(`/profiles/${uid}`, { preserveSnapshot: true }).take(1);
});
} else {
return Observable.throw(new Error("No bros here"));
}
});
}
Any help would be greatly appreciated.
Solution as per forkJoin recommendation:
getContacts() {
this.contactList.length = 0;
this.data.getAuthenticatedUserContactList().subscribe(
(contact => {
contact.map(userContact => {
this.contactList.push(<UserProfile>(userContact));
});
}),
(error => console.error(`getContacts Error: ${error}`))
);
}
getAuthenticatedUserContactList() {
return this.afAuth.getAuthUser()
.mergeMap(user => this.database.list(`contact-lists/${user.uid}`).take(1))
.mergeMap((contactList) => {
return Observable.forkJoin(contactList.map(element => this.getProfileWithUid(element.userId).map(userProfile => userProfile.val())))
});
}
getProfileWithUid(uid: string) {
this.profileObject = this.database.object(`/profiles/${uid}`, { preserveSnapshot: true });
return this.profileObject.take(1);
}
In the second flatMap() operator you're not returning anything when this condition is true:
contactList && contactList.length > 0
The return statement is inside forEach callback.
I don't know what's the logic of your app but the callback to flatMap always needs to return an Observable (or something listed here: http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObservableInputDoc.html)