I'm trying to understand how to consume observable sequences and how to recover from errors.
My example is a bit contrived but my real implementation is a bit too complex to show here. Anyway, I have someObservable that emits values when the user clicks in the UI. This should trigger a request to the API (GET/POST/etc). The problem is that if the API returns an error, postData isn't called anymore after that.
I've make an example here that shows the problem. I've found that I can use Rx.Observable.of instead of Rx.Observable.throw to keep things running. But that will emit a value to the subscribe function. And in my real application postData is a service that is reused by multiple parts of the application and I'm not sure that I want to use of instead of throw there.
So my question is, how do you normally handle things like these?
let someObservable = Rx.Observable.timer(1, 1000);
someObservable
.switchMap(data => {
return postData(data);
})
.catch(error => {
console.log("POST failed");
})
.subscribe(val => console.log("Success: " + val));
function postData(data) {
console.log("Will post " + data);
if (data !== 2) {
return Rx.Observable.of(true);
}
else {
return Rx.Observable.throw(new Error("400 Bad Request or something"));
}
}
http://jsbin.com/naroyeyive/3/edit?js,console
If you want to send the request again, you may want to look into retry and retryWhen instead of catch.
catch will use the returned Observable as new "source". See this bin for an example.
retry(When) will "re-subscribe" whenever an error occurs, based on the configuration you pass to the operators. You can find examples and more information here: https://www.learnrxjs.io/operators/error_handling/retrywhen.html
Related
I'm new to the javascript world and have been tinkering with Actions on Google. I had an action that was previously running but after attempting to simplify my code, I've been running into a new error I cannot seem to figure out. The code is supposed to make a call to a database and return information to the user based on the date they have selected.
Problem:
The code is supposed to make a call to a database and return information to the user based on the date they have selected. The intent seems to break down when I call the URL using axios. When I test my function I receive the following error from the Google Cloud Platform: "Error: No response has been set. Is this being used in an async call that was not returned as a promise to the intent handler?"
app.intent('Moon', (conv, {date}) => {
// Sets date to today if none given
if (!date) {
date = new Date().toISOString();
}
// Slices date string to match date format in database
const dateSlice = date.slice(5, 9);
// Call to row in dabase for the given date
function getData() {
return axios.get(`example.com/search?Date=${dateSlice}`);
}
return getData().then(res => {
res.data.map(con => {
conv.ask(`On X date there will be a ${con.Object1}`);
});
});
});
I don't know much about Promise and await but that seems to be the issue. I'm not sure how I was able to get my code to run before without these objects. I've tried to insert a Promise object before my return but it makes the rest of the function unreachable. I also checked to see if Axios had any updates but it has not, I am on the latest version. Does the error have to do with one of the returns perhaps?
It could be related to Promises, but you seem to be handling them correctly.
The call to axios.get() is returning a Promise...
... which you are returning in getData()...
... which is returned in the 'Moon' Intent handler as part of the getData().then() block.
I suspect more that this is a logic problem. If res.data is an empty array, then there will be no calls to conv.ask(), so you end up not asking anything.
There is also a problem if res.data has more than two items. In this case, you'll generate an error because you've replied with more than two "simple" responses.
Either way - you may wish to log res and/or res.data to make sure you're getting back what you think.
I am using angularjs for a web site where I have to download data every seccond. To do that I am using a WebSocket, the problem is that I am sending a lot of different requests of different types. To elaborate the request I use Promises but some times the answers get to the wrong function. Let me make an example:
So I have a service that is making the request and gets the result as Promise.
socketService.$inject = [];
function socketService(){
let service = this;
let server = new WebSocket('ws://localhost:8090');
let isOpen = false;
server.onopen = function(){
isOpen = true;
};
server.onerror = function(){
//TODO handle error
};
service.sendRequest = function(action, data){
return new Promise(function (resolve, reject) {
if (isOpen) {
server.send(encodeRequest(action, data));
server.onmessage = function (message) {
resolve(JSON.parse(message.data));
}
}
server.onopen = function () {
server.send(encodeRequest(action, data));
server.onmessage = function(message) {
resolve(JSON.parse(message.data));
isOpen = true;
}
};
server.onerror = function (error) {
reject(error);
}
})
};
}
And I use it like this:
socketService.sendRequest('get_events', {})
.then(function (response) {
//DO SOMETHING WITH THE RESPONSE
return socketService.sendRequest('get_history', {
fromDate: value,
toDate : value,
tag : value,
event : value
})
})
.then(function (response) {
//DO SOMETHING WITH THE RESPONSE
});
The problem is that if I make the requests like this (when one promise is resolved I call the other it works perfectly) but if instead I do for example some requests every second and then in the same time I make a new request (socketService.sendRequest(...)) the results get messed up.
I figured out that the problem is that if I make a request and before I get the result of the first request I make another one, the response of the first request goes to the second one.
I don't understand why this happens because I return every time a new Promise, so it should resolve the response of that Promise instead of resolving the response of the next Promise.
I read that once a promise is resolved it cannot be used again (it returns the last result over and over) but I create a new one every time.
Can someone explain to me what happens and how I could fix this?
You'll need to put a unique ID into the messages so that you can ensure that each response gets correlated to the correct request. But before diving into coding this, you may want to look at json rpc 2.0. This is a standard, with a couple of different javascript implementations, that will handle most of this for you. I have had good luck with https://github.com/jershell/simple-jsonrpc-js
You also asked why your Promise code is not working the way you expect it to. The answer is... well, Promise doesn't work the way you expect it to. :-) OK, let me try to be more helpful. Your Promise code is altering the server's callback methods (onOpen, onMessage, etc.) so that they will call the promise's "resolve" function. The problem is that, even though you have multiple promises, you only have one server. So each Promise you create just points the server callback methods to a new resolve function.
But... skip all that. Someone else has already figured this out for you. Use JSON RPC 2.0.
Duncan
I was also facing similar issues, as WebSocket message work as events. I have written one library where we can write Promise based send and receive the message.
https://github.com/pathikdevani/linka
What is the "best" (common) way to make sure that my Angular HTTP request only returns the newest response data. (I am using Angulars HttpClient)
Lets say the user submits a new request before the response of the previous request is returned. My backend needs more time to process the first request so the second request returns before the first one -> the view get's updated and later the first request returns. Now the view gets updated with old data. Thats obviously not what I want.
I found this solution to cancel the request but is that really the only way? Are there any build in functionalities to achive that?
if ( this.subscription ) {
this.subscription.unsubscribe();
}
this.subscription = this.http.get( 'myAPI' )
.subscribe(resp => {
// handle result ...
});
I know there is the switchMap() operator which only returns the latest data but as far as I know you can only use it inside of an observable. In my case I am not observing any input changes. I simply call a function on form submit via (ngSubmit)="doRequest()" directive in HTML-Template.
// get called on submit form
doRequest() {
this.http.get('myAPI')
.subscribe(resp => {
// handle result ...
});
}
Is there a way to use the switchMap operator or do you have any other solutions? Thanks for taking your time :)
PS: Of course in my real project I have an api service class with different functions doing the requests and just returning http response observables. So I subscribe to such an observable in doRequest() function.
you just make the clicks / requests a stream that you can switch off of... the user's requests / clicks are a stream of events that you can observe and react to.
private requestSource = new Subject();
request() {
this.requestSource.next();
}
ngOnInit() {
this.requestSource.switchMap(req => this.http.get('myApi'))
.subscribe(response => console.log(response, "latest"));
}
modern client programming rule of thumb: everything is a stream, even if it doesn't look like one right away.
Edit: In the case of rxjs 6 or latest version, add pipe operator with the above code.
this.requestSource.pipe(switchMap(...));
I'm working on an internal page that allows a user to upload a CSV with resources and dates, and have the page add all the scheduling information for these resources to our management software. There's a pretty decent API for doing this, and I have a working model, but it seems...cludgy.
For each resource I have to start a new session, then create a new reservation, then add resources, then confirm that the reservation isn't blocked, then submit the reservation. Most of the calls return a variable I need for the next step in the process, so each relies on the previous ajax call.
Currently I'm doing this via nested ajax calls similar to this:
$.ajax('startnewsession').then($.ajax('createreservation').then('etcetc'))
While this works, I feel like there has to be an easier, or more "proper" way to do it, both for cleaner code and for adaptability.
What you're doing is correct, assuming you can't change the API you are communicating with.
There's really no way of getting around having to do some sort of nested ajax calls if you need the response data of the previous one for the next one. Promises (.then) however make it a bit more pretty than having to do callbacks.
The proper solution (if possible) would of course be to implement your API in such a way that it would require less roundtrips from the client to the server. Considering there's no user input in between each of these steps in the negotiation process for creating a reservation, your API should be able to complete the entire flow for creating a reservation, without having to contact the client until it needs more input from the user.
Just remember to do some error handling between each of the ajax calls in case they should fail - you don't want to start creating the following up API calls with corrupt data from a previously failed request.
var baseApiUrl = 'https://jsonplaceholder.typicode.com';
$.ajax(baseApiUrl + '/posts/1')
.then(function(post) {
$.ajax(baseApiUrl + '/users/' + post.userId)
.then(function(user) {
console.log('got name: ' + user.name);
}, function(error) {
console.log('error when calling /users/', error)
});
}, function(error) {
console.log('error when calling /posts/', error)
});
Short answer: as usual I'm trying to do some chains like this:
ajaxCall1.then(
response => ajaxCall2(response)
).then(
response => ajaxCall3(response)
)
I'm trying to avoid using of when. As usual I (and bet you too) have 1 ajax call (for form submit), sometimes 2 chaining ajax calls, for an example, if I need to get data for table, first query for total rows count, and if count greater than 0, another call for data. In this case I'm using:
function getGridData() {
var count;
callForRowsCount().then(
(response) => {
count = response;
if(count > 0) {
return callForData();
} else {
return [];
}
}
).then(response => {
pub.fireEvent({
type: 'grid-data',
count: count,
data: response
})
})
}
publisher trigger event, and I have all my components updated.
In some realy rare cases, I need to use when. But this is always bad design. It happen in case, when I need to load pack of additional data before of main request, or when backend not support bulk update, and I need to send pack of ajax calls to update many of database entities. Something like this:
var whenArray = [];
if(require1) {
whenArray.push(ajaxCall1);
}
if(require2) {
whenArray.push(ajaxCall2);
}
if(require3) {
whenArray.push(ajaxCall3);
}
$.when.apply($, whenArray).then(() => loadMyData(arguments));
I'm working on an Angular application which shows a list of items fetched from a RESTful API. The content of the list depends on a query.
The query can be passed either by filling in an input field, using a submit button or by adding it to the URL as query parameter.
To make sure everything runs in order and prevent asynchronous issues, I’m using RxJS.
Now I’m wondering how I can handle errors, since they can occur in the middle of a stream, eg. when the HTTP-request fails.
The query as input stream
These are two Observables, which both send a sequence of queries.
// first observable for the submit button
submitFormObservable = $scope.$createObservableFunction('search');
// second observable for when the input value changes
inputObservable = $scope.$toObservable('query')
.map(function (change) {
return change.newValue;
});
Fetching for the results
The Observable below triggers when the query has been changed and fetches the results from the API.
var mainStream = Rx.Observable.merge([submitFormObservable, inputObservable])
.where(function (query) {
return query && query.length > 0;
})
.debounce(400)
.distinctUntilChanged()
.select(getResultsForQuery)
.switchLatest();
Handling errors
Now I don’t know how to handle errors when eg. the getResultsForQuery throws an error.
I would like to display the error instead of the results, but prevent the Observable from handling new events.
Currently I've solved it by creating two new streams from the Observable, one to handle the result when successful and one when an error occurs. The response data for when the query was invalid contains an error property.
Success stream
// stream containing the album information from LastFm
mainStream
.filter(function (response) {
return !response.data.error;
})
.map(function (response) {
return response.data.result;
})
.subscribe(function (result) {
$scope.error = undefined;
$scope.result = result;
});
Error stream
mainStream
.filter(function (response) {
return response.data.error;
})
.map(function (response) {
return response.data;
});
.subscribe(function (error) {
$scope.result = [];
$scope.error = error;
});
Possible solutions
I've read about throwing and catching errors, but the problem here is the stream seems to stop after the first error and doesn't trigger new events.
Using onErrorResumeNext is described in the docs to ignore errors and make sure the stream continues after an error. But I can't find a way to correctly 'handle' the error and show it to the end user.
Question
Is there a recommended approach on how to handle errors in a situation like this? Is it necessary to create two streams for this, or is it recommended to throw an exception?
It's important that the user knows something went wrong, and the stream doesn't stop after the first error.
The issue with catching logic is that it effectively terminates the sequence before it, which is why if you use it in your top level stream it will stop after a single exception. I would recommend that you wrap your catch logic inside of a flatMapLatest. Then you can catch on the inner stream and transform the data to conform with what your downstream observers expect.
Something like this:
var mainStream = Rx.Observable.merge([submitFormObservable, inputObservable])
.where(function (query) {
return query && query.length > 0;
})
.debounce(400)
.distinctUntilChanged()
.selectSwitch(function(input) {
return getResultsForQuery(input)
.map(function(response) {
return {result : response.data.result};
})
.catch(function(e) {
return Rx.Observable.just({error : error});
});
});
mainStream.subscribe(function(r) {
$scope.result = r.result || [];
$scope.error = r.error;
});