I can't understand why onCompletion is not called when an error occures (e.g http 401/404). Shouldn't be the expected behaviour?
this.http.get('http://localhost/xxx')
.map((res) => res.json())
.subscribe(
(res) => {
console.log('onSuccess');
console.log(res);
},
(err) => {
console.log('onError');
console.log(err);
},
() => {
console.log('onCompletion');
});
In an Observable Execution, zero to infinite Next notifications may be delivered. If either an Error or Complete notification is delivered, then nothing else can be delivered afterwards.
http://reactivex.io/rxjs/manual/overview.html#executing-observables
There are 3 types of events:
next
error
complete
And they occur according to this (RegExp) scheme: n*(e|c)
The documentation states this too, as Julia points out.
Related
I have the following chain.
return axios
.get(actionUrl, {
params: {
action: 'action3'
},
})
.finally(() => axios.get(actionUrl, {
params: {
action: 'action3'
},
}))
.finally(() => axios.get(actionUrl, {
params: {
action: 'action6'
},
}))
.finally(() => axios.get(actionUrl, {
params: {
action: 'action1'
},
}))
I have to sequentially call different endpoints in order even if the previous one fails. However in case an endpoint timeouts I want to break the chain. Is it achievable without using .then and .catch and repeating the same code in them?
Thanks.
The finally function is there precisely to make sure that the function inside runs even if there is an exception. You can get the behaviour you want by using just one finally like this:
axios.get()
.then(() => doStuffOnSuccess())
.finally(() => {
axios.get().then(() => doFinallyStuff1())
.then(() => doFinallyStuff2())
.then(() => doFinallyStuff3())
.catch(e => console.error("Finally had trouble",e));
});
This way if anything within the finally function times out or fails it will break the chain. By having the final catch you will avoid it throwing back further up the chain.
This assumes that you are using finally correctly and everything in that should always get executed after the previous calls finish even if there are errors.
This is achievable with then and catch. You should not use finally if you don't want the callback to run in case of an error.
I have to sequentially call different endpoints in order even if the previous one fails. However in case an endpoint timeouts I want to break the chain
So you want to not call them when the previous one fails (with a timeout), all you want to do is to ignore non-timeout errors. That's what catch should be used for:
function callEndpoint(action) {
return axios.get(actionUrl, { params: { action } }).catch(err => {
if (isTimeout(err))
throw err
else
; // ignore the error, return undefined
})
}
Then just chain them:
callEndpoint('action3').then(() => callEndpoint('action6')).then(() => callEndpoint('action3'))
Are you familiar with async/await? Generally you shouldn't chain finally like this, it's always better to create recurent function for example:
const fetchSomething = async () => {
try {
const result = await axios.get();
if (...when fetching should stop...) {
return result;
}
return fetchSomething();
} catch(error) {
return fetchSomething();
}
}
But with reccurent function is extremely important to create some kill switch to prevent executing it forever - for example set some kind of timeout, 1 minute or so and if this limit is exceeded then stop executing.
It will be probably even more easier with generators and yield but I never used this solution
Below is my stylized code :
let the_function = (req, res, next) => {
Promise
.map(promises_params, (x) => {
return get_promise(x);
}, {concurrency: 20}
).then((values) => {
let reduceFunc = (acc, currV, currI) => {
if (something_bad) {
next(new Error("something bad happened");
return;
} else {
return acc;
}
};
let result = values.reduce(reduceFunc,[]);
res.send(result);
}).catch((err) => {
res.send(err);
});
};
The idea is that I am running a bunch of promises then reducing the values I get to a single object which I then send back to the user.
When something_bad happens within the reduce function reduceFunc, I would like to get a proper error sent back to the user. But in my current setup, the node.js server fails and has to reboot after I get :
_http_outgoing.js:494
throw new ERR_HTTP_HEADERS_SENT('set');
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
and the user gets an error which is not the one I would like her to see.
How to make sure the error I want the user to see is output cleanly without killing the server in the meantime ?
The reason you are running into an error is because calling next does not stop the reduce function from executing. The reduce function continues to run and eventually res.send(results) is called. This becomes a problem because the next route handler is apparently also sending a response, but only one response can be sent for a request so you get an error.
The fact that you are not calling next in your catch handler indicates that you don't really want to use next. Since that is the case you can simply throw the error so that execution of the reduce function is haulted and the catch handler is invoked.
let the_function = (req, res, next) => {
Promise
.map(promises_params, (x) => {
return get_promise(x);
}, {concurrency: 20}
).then((values) => {
let reduceFunc = (acc, currV, currI) => {
if (something_bad) {
throw new Error("something bad happened");
} else {
return acc;
}
};
let result = values.reduce(reduceFunc,[]);
res.send(result);
}).catch((err) => {
res.send(err);
});
};
Now a response is only sent once. It is sent if the reduce function completes without something_bad happening, or when an error occurs, but never both.
Okay, this is a pretty simple question.
I am returning a 400 status error along side a message "Index should have 6 digits" message back in my response.
I am using React for my front-end. When I log the body of the response using the fetch api [console.log(response.json())], I can see that I receive it properly.
But for some reason I cannot access it any any way.
Also, I don't know why the status statusText field in the response is empty.
What am I doing wrong?
What is the defacto standard for exception handling from server side?
Thank you.
Edit - code:
errorHandler = (response) => {
if (!response.ok){
console.log(response.json());
throw Error(response.statusMessage);
}
else {
return response;
}
};
addStudent = (student) => {
createStudent(student)
.then(this.errorHandler)
.then((response) => {
console.log('new student status: ', response.status);
this.loadData();
})
.catch((error) => {
console.log(error);
});
};
You'll see that your're getting a Promise object back in your console, this means that your request simply hasn't fulfilled yet.
Chain onto the promise with .then(response => { // handle the response here })
The code should look similar to this:
fetch('/endpoint').then(response => {
console.log(response)
}
Hope this helps!
What I do is what Mike wrote above.
fetch("endpoint")
.then((response) => response.json())
.then((data) => console.log(data.message));
I have a mobile app I'm building and right now I'm working on authentication. Before I hit my home page I need to hit a variety of endpoints on an API I've built before I can display data to the user.
All the endpoints are returning the correct data when tested in Postman, however I'm getting a null value in my second async call when I utilize it in my app.
I'm sure it has something to do with the order in which these calls are made, so I was just looking for some help as to how I can properly wait for one call to finish before starting another one.
public login() {
this.showLoading();
this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
.subscribe(
res => {
console.log(res);
localStorage.setItem("UserId", res.toString());
},
err => {
console.log(err);
});
this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL
.subscribe(
res => {
console.log(res);
localStorage.setItem("EmployeeId", res.toString());
},
err => {
console.log(err);
});
this.authService.login(this.registerCredentials)
.subscribe(
data => {
this.loading.dismissAll();
console.log('User logged in successfully! ', data);
this.nav.push(TabsPage);
localStorage.setItem("Username", this.registerCredentials.username);
localStorage.setItem("isLoggedIn", "true");
},
error => {
this.loading.dismissAll();
this.showAlert("Uh oh!", "Something went wrong. Please re-enter your login credentials or check your connection.");
console.log(error);
});
}
Your original code has a bug that leads to this error. You have three calls in your code which I will call A), B), and C):
A) this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
B) this.userService.getEmployeeIdFromUserId(localStorage.getItem("UserId")) // THIS RETURNS NULL
C) this.authService.login(this.registerCredentials)
What you need to understand about RXJS is the difference between a cold Observable (which represents all information required to start an async operation) and a hot Observable (which is an Observable with the async operation already started).
The three calls A), B) and C) merely build cold observables which are started the moment you call .subscribe() on them. So by the time B) is built, A) is already started but has not completed yet. So the call to localStorage.getItem("UserId") will return null, because A) has not yet invoked its subscriber's next callback.
So what you want to do is for B) to wait on A). Also instead of stuffing something into global state (localStorage) it's probably better to flow the result from A) through to B). Enter the .mergeMap() operator:
this.userService.getUserIdFromUserName(this.registerCredentials.username) // WORKS
.map(res => res.toString())
.do(userId => localStorage.setItem("UserId", userId)) // cleanly separate side-effects into .do() calls
.mergeMap(userId => this.userService.getEmployeeIdFromUserId(userId))
.map(res => res.toString())
.do(employeeId => localStorage.setItem("EmployeeId", employeeId))
.subscribe(
employeeId => {
console.log(employeeId);
},
err => {
console.log(err);
});
The nice thing about rxjs is that error handling just works all the way through your Observable chain.
If you can execute C) in parallel, have a look at .forkJoin().
Finally, if you need a hands on explanation of .mergeMap() have a look at this answer: SwitchMap vs MergeMap in the #ngrx example
This should work.Don't forget import 'rxjs/Rx'
this.userService.getUserIdFromUserName(this.registerCredentials.username)
.map(res => res.toString())
.do(userId => {
console.log(res);
localStorage.setItem("UserId", userId);
})
.flatMap(userId => {
return this.userService.getEmployeeIdFromUserId(userId);
})
.do(res => {
console.log(res);
localStorage.setItem("EmployeeId", res.toString());
})
I have implemented some error handling code in my main function as below. It uses the catch operator to filter and report on errors in one stream and ignore them in another. This allows me to know about and report on errors that occur with requests whilst not failing the entire stream so that subsequent requests can continue.
For reasons that might not be apparent in the code snippets below I am impementing a custom driver to request and handle data. I'm not using the cycle http driver.
Here's my code that successfully reports an error:
function main(sources) {
// Catch driver errors so they can be logged
const error$ = sources.CustomDriver
.map(x => x.catch(e => Rx.Observable.just(e)))
.flatMap(p => p)
// Filter out the errors to deal with requests that did not fail
const data$ = sources.CustomDriver
.map(x => x.catch(e => Rx.Observable.empty()))
.flatMap(p => p)
return {
CustomDriver: Rx.Observable.just('initial event'),
Log: data$,
Error: error$
}
}
Cycle.run(main, {
CustomDriver: makeCustomDriver(),
Log: msg$ => { msg$.subscribe(
msg => console.log('LOG: ', msg),
err => console.log('problem with Log driver: ', err),
() => console.log('Log Completed')
) },
Error: msg$ => { msg$.subscribe(
e => console.log('ERR: ', e),
err => console.log('problem with Error driver:', err),
() => console.log('Error Completed')
) }
})
function makeCustomDriver() {
return function customDriver(requests$) {
return requests$
.map(request => Rx.Observable.fromPromise(makeFailedRequest()))
}
}
function makeFailedRequest() {
console.log('some API request')
return Promise.reject('error')
}
The output is as follows:
some API request
some API request
Log Completed
ERR: error
Error Completed
On the plus side the error is reported. However, the API request is actually made twice, which is not what I expected to happen initially.
After learning some more RxJS and getting a better understanding of Hot and Cold observables I realised that I was creating two subscriptions to the CustomDriver stream (one for error$ and one for data$) and because the CustomDriver Observable was cold it would repeat the Observable.just for each subscriber.
So I tried to make my CustomDriver Observavble hot with share:
function makeCustomDriver() {
return function customDriver(requests$) {
return requests$
.map(request => Rx.Observable.fromPromise(makeFailedRequest()))
.share()
}
}
With that change, the output is as follows:
some API request
Error Completed
Log Completed
So I managed to get rid of the duplicate request but the error was swallowed up in the process.
What is happening with share that causes the errors to be lost and how can I avoid duplicate requests without losing errors?
.shareReplay(1) appears to give the desired result.
There is a factory for making custom drivers of that kind that you want (from Promises) https://github.com/whitecolor/cycle-async-driver it includes, helpers for dealing with errors (success and failure).
You can created drivers just like that:
import {makeAsyncDriver} from 'cycle-async-driver'
customDriver = makeAsyncDriver(
(request) => requestHanderThatReturnsPromise(reques)
)