RxJS - observable doesn't complete when an error occurs - javascript

When I create an observable from scratch, and have the observer error, then complete, the done part of the subscription never is invoked.
var observer = Rx.Observable.create(function(observer){
observer.onError(new Error('no!'));
observer.onCompleted();
})
observer.subscribe(
function(x) { console.log('succeeded with ' + x ) },
function(x) { console.log('errored with ' + x ) },
function() { console.log('completed') }
)
The output is:
errored with Error: no!
I'd expect it to be:
errored with Error: no!
completed
If I change the code to invoke onNext instead of onError, the observable properly completes:
var observer = Rx.Observable.create(function(observer){
observer.onNext('Hi!');
observer.onCompleted();
})
observer.subscribe(
function(x) { console.log('succeeded with ' + x ) },
function(x) { console.log('errored with ' + x ) },
function() { console.log('completed') }
)
I get the expected output:
succeeded with Hi!
completed
Why does it not complete when an error has occured?

That's because an error means completion, so the callback associated to onCompleted never gets called. You can review here Rxjs contract for observables (http://reactivex.io/documentation/contract.html) :
An Observable may make zero or more OnNext notifications, each representing a single emitted item, and it may then follow those emission notifications by either an OnCompleted or an OnError notification, but not both. Upon issuing an OnCompleted or OnError notification, it may not thereafter issue any further notifications.`
For error management, you can have a look at :
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/errors.md

Another and probably the simplest solution might be using the add() function.
The statement will be always executed regardless an error
occured or not (like the finally statement in most programming languages).
observer.subscribe(
function(x) { console.log('succeeded with ' + x ) },
function(x) { console.log('errored with ' + x ) },
function() { console.log('completed') }
)
.add(() => {
console.log("Will be executed on both success or error of the previous subscription")
);

While I was having the same question, I bumped into this github issue.
Apparently finally method of Observable object needs to be used in this case.
Quoting from Aleksandr-Leotech from that thread:
Complete and finally are totally different things. Complete means that
the observable steam was finished successfully. Because you can have
many success calls. Finally means that steam has ended, either
successfully or not.
It is not obvious with HTTP requests, but imagine two additional
scenarios.
Mouse events. You will be receiving a never-ending steam of success
callbacks, but you will never receive finally or complete, because
user events will never stop (unless you trigger an exception with
buggy code, then you will get error and finally).
Working with web sockets. You will get multiple success callbacks, but at some point in time your communication with back end will stop and you will get both complete and finally unless you have some errors, which will call error and finally.
So, you might be getting multiple or no success calls, zero or one error call, zero or one complete and zero or one finally.

To run a callback when observable completes or errors, you should use finalize.
Ex:
this.service.yourObservable
.pipe(
finalize(() => {
// * This will always run when observable finishes the stream
console.log("Finally!");
// * callback for finally
})
).subscribe(
{
next: () => { // * Callback for success },
error: () => { // * Callback for error },
complete: () => {// * This gets called only on success }
})

Related

Promise: skip all fulfill and reject reactions but execute .finally

I have function called request:
function request (endpoint) {
return axios.request(endpoint).then(api.onSuccess).catch(api.onError)
}
api.onSuccess:
onSuccess (response) {
let breakChain = false
... some logic goes here ...
return breakChain ? (new Promise(() => {})) : response
}
api.onError:
onError (error) {
let breakChain = false
... some logic goes here ...
if (breakChain) {
return new Promise(() => {})
} else {
throw error
}
}
api holds a lot of functions that represent different API calls based on provided endpoints data and return request(endpoint).
Currenly I have code, as you can see, that return Promise with empty executor that is always in pending state to stop the chain of subsequent .then(...) and .catch(...) handlers from execution as they just infinitely wait for that Promise to settle. This is done to handle certain API responses that have common response handling (like responses with code >= 500).
The problem is that now I need a call to .finally() (like in Vue cookbook - https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html#Dealing-with-Errors) to restore some component's state nevertheless of whether there is an error or not, but this approach of pending Promise is an obstacle.
The question is: is it possible to skip all subsequent .then(...) and .catch(...) calls within one of such handlers to go directly to .finally()?
Update. I haven't mentioned the important bit - api.onSuccess and api.onError are basic handlers. In another application components there are additional handlers appended to the end of that basic chain presented in request function. Usual chain of some API call has a following resulting form:
return axios.request(endpoint).then(api.onSuccess).catch(api.onError).then((response) => {...}).catch(() => {...}).finally(() => {...})
(sometimes there is no .finally() or .catch(...) block)
Is it possible to skip all subsequent .then(...) and .catch(...) calls within one of such handlers to go directly to .finally()?
No.
Currenly I stop the chain by just infinitely waiting - yet this approach of pending Promise is an obstacle.
Indeed, don't do that. You can skip then handlers by using rejections (exceptions) for flow control, but the more appropriate way is to handle this by nesting the part of the chain to be skipped inside an if statement.
This is done to handle certain API responses that have common response handling (like responses with code >= 500)
For that, you should use something like
return axios.request(endpoint).then(response => {
…
}).catch(error => {
if (api.handleCommonError(error)) return; // returns false if it couldn't handle the error
…
}).finally(() => {
…
});
Yes, you cannot hide this kind of error handling inside an api.request function.
You can use async and await. All modern browsers support them, and your bundler can make them compatible with older browsers.
For example:
async function request (endpoint) {
try {
const response = await axios.request(endpoint);
return api.onSuccess(response);
} catch (err) {
api.onError(err);
} finally {
// Always executed, even if no error was thrown
}
}
You can also do it more traditionally:
function request (endpoint) {
return axios.request(endpoint).then(api.onSuccess, api.onError).then(() => {
// Code is always executed after error / success
}
}

RxJS 5 task queue, continue if a task fails

Imagine that we have an HTML page that fires AJAX requests. We want to make sure that AJAX requests are executed in order. The next AJAX request won't be fired until the previous one completes or errors.
I have tried to model this via a task queue using RxJS concatMap. Each AJAX request is modeled as an Observable. Everything is working great if AJAX request completes successfully, however if it errors, then the next task in the queue is not executed.
Here is an example, that uses setTimeout() to simulate long running async tasks:
function identity(observable) {
return observable;
}
function createTaskQueue() {
var subject= new Rx.Subject();
subject
.concatMap(identity)
.onErrorResumeNext(Rx.Observable.of('error'))
.subscribe(function(data) {
console.log('onNext', data);
},
function(error) {
console.log('onError', error);
});
return {
addTask: function(task) {
subject.next(task);
}
}
}
function createTask(data, delay) {
return Rx.Observable.create(function(obs) {
setTimeout(function() {
obs.next(data);
obs.complete();
}, delay);
});
}
function createErrorTask(data, delay) {
return Rx.Observable.create(function(obs) {
setTimeout(function() {
obs.error('Error: ' + data);
obs.complete();
}, delay);
});
}
var taskQueue = createTaskQueue();
taskQueue.addTask(createTask(11, 500))
taskQueue.addTask(createTask(22, 200));
taskQueue.addTask(createErrorTask(33, 1000));
taskQueue.addTask(createTask(44, 300));
taskQueue.addTask(createErrorTask(55, 300));
taskQueue.addTask(createTask(66, 300));
Here is an executable example: https://jsfiddle.net/artur_ciocanu/s6ftxwnf/.
When I run this code the following is printed to the console:
onNext 11
onNext 22
onNext error
Which is expected, but I wonder why the other tasks like 44, 55, etc are not executed.
I am pretty sure I am doing something stupid with onErrorResumeNext() or may be the whole approach is totally wrong.
Any help is very much appreciated.
If you read the documentation of onErrorResumeNext,
Continues an observable sequence that is terminated normally or by an
exception with the next observable sequence or Promise.
What that means is that when your source observable will encounter an error, it will switch to whatever you passed to onErrorResumeNext. What happens here is that Rx.of(...) terminates immediately after emitting its value. Hence the behavior you observe.
So in short, you don't want onErrorResumeNext here.
You could instead .catch(...) the stream which could emit an error. So, something like :
subject
.concatMap(obs => obs.catch(Rx.Observable.of('error')))
.subscribe(...)
the idea of an error in observables is the same as in regular function. Meaning if you throw an error in a regular function - function will not return anything. The same is with observables - if observable emits an error that means stream is completed and no more values is coming. So yes it is fundamentally wrong.
The better (right) approach would be to to have a stream of responses where next value can be either successful response or and error response. And if you need to separate them you can split responses stream into two successful/error responses later on.
Hope that helps.

node.js async request with timeout?

Is it possible, in node.js, to make an asynchronous call that times out if it takes too long (or doesn't complete) and triggers a default callback?
The details:
I have a node.js server that receives a request and then makes multiple requests asynchronously behind the scenes, before responding. The basic issue is covered by an existing question, but some of these calls are considered 'nice to have'. What I mean is that if we get the response back, then it enhances the response to the client, but if they take too long to respond it is better to respond to the client in a timely manner than with those responses.
At the same time this approach would allow to protect against services that simply aren't completing or failing, while allowing the main thread of operation to respond.
You can think of this in the same way as a Google search that has one core set of results, but provides extra responses based on other behind the scenes queries.
If its simple just use setTimout
app.get('/', function (req, res) {
var result = {};
// populate object
http.get('http://www.google.com/index.html', (res) => {
result.property = response;
return res.send(result);
});
// if we havent returned within a second, return without data
setTimeout(function(){
return res.send(result);
}, 1000);
});
Edit: as mentioned by peteb i forgot to check to see if we already sent. This can be accomplished by using res.headerSent or by maintaining a 'sent' value yourself. I also noticed res variable was being reassigned
app.get('/', function (req, res) {
var result = {};
// populate object
http.get('http://www.google.com/index.html', (httpResponse) => {
result.property = httpResponse;
if(!res.headersSent){
res.send(result);
}
});
// if we havent returned within a second, return without data
setTimeout(function(){
if(!res.headersSent){
res.send(result);
}
}, 1000);
});
Check this example of timeout callback https://github.com/jakubknejzlik/node-timeout-callback/blob/master/index.js
You could modify it to do action if time's out or just simply catch error.
You can try using a timeout. For example using the setTimeout() method:
Setup a timeout handler: var timeOutX = setTimeout(function…
Set that variable to null: timeOutX = NULL (to indicate that the timeout has been fired)
Then execute your callback function with one argument (error handling): callback({error:'The async request timed out'});
You add the time for your timeout function, for example 3 seconds
Something like this:
var timeoutX = setTimeout(function() {
timeOutX = null;
yourCallbackFunction({error:'The async request timed out'});
}, 3000);
With that set, you can then call your async function and you put a timeout check to make sure that your timeout handler didn’t fire yet.
Finally, before you run your callback function, you must clear that scheduled timeout handler using the clearTimeout() method.
Something like this:
yourAsyncFunction(yourArguments, function() {
if (timeOutX) {
clearTimeout(timeOutX);
yourCallbackFunction();
}
});

Angular 2 Http polling not delivering errors

I am trying to poll a REST API to update a data table which is working fine with the following code:
pollData(url, interval) {
return Rx.Observable.interval(interval)
.mergeMap(() => this.http.get(url));
}
// get data
this.dataService.pollData(this.url, this.updateInterval)
.subscribe(
data => console.log(data),
err => console.log(err),
() => console.log('done'));
The problem is that error and complete never get called. Any suggestions to get this working with onError and onCompete would be greatly appreciated. Thanks!
About the onComplete call on the observer, it will be effected only when the source observable finishes. This means when the observable returned by pollData completes. As you are currently polling with no exit condition, then naturally your observable never completes.
To have this observable complete, you need to come up with an exit condition :
timeout (for instance, poll for X seconds, then stop polling)
number of polls
pollData-based condition (for instance, if no changes detected after X consecutive polling)
external completion signal
any other condition which makes sense to your use case
All these conditions are easy to implement with RxJS through they will require you to update the code of the pollData function.
For instance for the external completion signal, you could write :
// defining somewhere the subject for signalling end of polling
stopPollingS = new Rx.Subject();
// somehow pass this subject as a parameter of the polling function
pollData(url, interval, stopPollingS) {
return Rx.Observable
.interval(interval)
.mergeMap(() => this.http.get(url))
.takeUntil(stopPollingS);
}
// somewhere in your code when you want to stop polling
stopPollingS.onNext(true);
About the onError call on the observer, , I am not sure I get what is happening. Have you tried provoking an error and check the onError handler of your observer is indeed called? If there is no error, it is quite obvious that the onError will not be called.
Just in case anyone was wanting to know how I went about solving this problem and implemented the functionality that was required. Basically I just needed to wrap the observable in another and return the error as a data.
initiatePolling(url, interval) {
var http = this.http;
return Rx.Observable.create(function (observer) {
// initial request (no delay)
requestData();
var timerId = setInterval(requestData, interval);
function requestData() {
var subscription = http.get(url).timeout(20000)
.subscribe(
result => {
observer.next(result);
subscription.unsubscribe();
},
err => {
observer.next(err);
subscription.unsubscribe();
},
() => {
subscription.unsubscribe();
});
}
return function () {
observer.complete();
window.clearInterval(timerId);
}
});
}

timeout for function (jQuery)

function getNames(){
// some code
}
This function can be done in one second, but sometimes it freezes itself and html block on the page (ajax inside) on infinite time.
I would like to have time limit for this function. If it doesn't finish in ten seconds, then abort it.
How to do this?
This is really easy with jQuery 1.5’s deferred promises.
The following example creates a Deferred and sets two timer-based functions to either resolve or reject the Deferred after a random interval. Whichever one fires first “wins” and will call one of the callbacks. The second timeout has no effect since the Deferred is already complete (in a resolved or rejected state) from the first timeout action.
// Create a Deferred and return its Promise
function asyncEvent() {
var dfd = new jQuery.Deferred();
setTimeout(function() {
dfd.resolve('hurray');
}, Math.floor(Math.random() * 1500));
setTimeout(function() {
dfd.reject('sorry');
}, Math.floor(Math.random() * 1500));
return dfd.promise();
}
// Attach a done and fail handler for the asyncEvent
$.when( asyncEvent() ).then(
function(status) {
alert( status + ', things are going well' );
},
function(status) {
alert( status + ', you fail this time' );
}
);
You can easily modify this example to suit your needs:
// Create a Deferred and return its Promise
function asyncEvent() {
var dfd = new jQuery.Deferred();
// Your asynchronous code goes here
// When the asynchronous code is completed, resolve the Deferred:
dfd.resolve('success');
setTimeout(function() {
dfd.reject('sorry');
}, 10000); // 10 seconds
return dfd.promise();
}
// Attach a done and fail handler for the asyncEvent
$.when( asyncEvent() ).then(
function(status) {
alert( status + ', things are going well' );
},
function(status) {
alert( status + ', you fail this time' );
}
);
If it is the function itself that's grinding away, just set a timer and either return or throw if it exceeds your maximum time period.
If, on the other hand, if the delay is caused by the AJAX request not returning, and you are using $.ajax(), you can always set the timeout setting. It should kill the request. Note that you need to avoid relying on the success callback, since it only gets called if the request succeeds. Instead, use complete callback to watch for failures. It will tell you if an error occurred. According to the documentation, this is available in 1.4 onwards. I'm not sure about pre-1.4, but I think it worked in 1.3 as well.
As suggested above, you can go with Web Workers if //some code is something that might take a long time sometimes (like waiting for large amounts of information from a slow server). That will allow background processing without locking the main page. Note that not all browsers support it yet.
You can find a nice introduction here.

Categories