RxJS error handling - javascript

I've got an issue with RxJS swallowing errors. So in my case I've got something like this:
function funcThatThrowsError(fn, ..args) {
return fn.bind(fn, ...args);
}
function fetchItems() {
Observable.fromPromise(
reqwest({
url: `${API_ROOT}${url}`,
method,
data,
contentType: "application/json"
})
).map(funcThatThrowsError(null, "someValue"))
}
const observableA = fechItems();
const observableB = ... ;
Observable
.combineLatest(
observableA,
observableB,
() => { }
)
.forEach(
() => { }, // success
(err) -> console.log(err.stack);
)
So basically I'm deliberately passing a null value as the fn parameter which causes the funcThatThrowsError to throw an exception.
The problem is that the error callback is not invoked in this case. Rather RxJS will use it's own thrower function
function thrower(e) {
throw e;
}
What's the best practise to deal with this scenario. I feel that there is something that I'm missing.

The exception is happening outside of the Observable. You are raising it while creating a function that you want to pass into the observable.
If you want the error handler at the end of the chain to handle it, you have to raise it inside of the operator chain:
function fetchItems() {
Observable.fromPromise(
request({
url: `${API_ROOT}${url}`,
method,
data,
contentType: "application/json"
})
).map(funcThatThrowsError(content => throw new Error(content), "someValue"))
}

Related

Javascript condition to use await of not

Is there a way I can write this block of code without all the repeated code for calling axios?
export const handler = async (event, useAwait) => {
const { path, data } = JSON.parse(event);
if (useAwait) {
return await axios(`https://example.com/api${path}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
} else {
// non-blocking
axios(`https://example.com/api${path}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
return true;
}
};
Put the promise into a variable, and then you can conditionally return it.
export const handler = async (event, useAwait) => {
const { data } = JSON.parse(event);
const prom = axios(`https://example.com/api${url}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
return useAwait ? (await prom) : true;
};
That said, you may as well return the Promise itself - awaiting something that you're returning immediately doesn't help since you're not immediately inside a try.
return useAwait ? prom : true;
But calling this function without returning the result looks like a bad idea because then you may get an unhandled rejection. You may wish to add a .catch to the Promise in that case to avoid that.
export const handler = (event) => {
const { path, data } = JSON.parse(event);
const prom = axios(`https://example.com/api${path}`, {
method: 'post',
headers: {
'api-key': key,
},
data: data,
});
prom.catch(()=>{}); // prevent uncaught rejection
return prom;
}
If you don't test the return value of handler to be strictly equal to true, calling code can rely on promise objects being truthy in a conditional expression as needed, and the logic can be simplified to an ordinary function that returns a promise without using await or taking a useAwait parameter.
The dummy catch handler added to prom is to prevent generating an uncaught-rejected-promise error if the caller doesn't handle rejection of the axios promise, and can be omitted if uncaught rejection errors are acceptable. How calling code is meant to operate without waiting for the axios call completed is unclear and not covered here.
One of the reasons for suggesting major simplifications is the // non-blocking comment in the posted code. Async functions always return a promise object when called with no blocking effect, either to continued execution of calling code or browser operation.
The only effective difference between using await or not (in the post) is the value used to resolve the returned promise by handler.

Catching errors thrown from nested asynchronous functions at the top-level

I am having trouble "catching" an error using either a try/catch or .catch statement, and I believe it is because the error is not being thrown in a "conventional" manner. At the top-level, I have an invocation of a function within an async function:
async function main() {
let computedValue;
try {
computedValue = await module.doComputation();
} catch (err) {
console.log("We caught the error!")
computedValue = null;
}
}
// in `module`
import $ from 'jquery';
export function doComputation() {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: location.href,
success: (data) => {
const text = $($.parseHTML(data)).find('#some-dom-element').text()
const total = text.match(/[+-]?\d+(\.\d+)?/g)[0]; // this line throws an error!
if (total > 0) {
resolve(total);
}
},
error: () => {
resolve(-1);
},
});
});
}
When I run this, the exception that is thrown from within doComputation is unhandled, and it looks like:
Uncaught TypeError: Cannot read properties of null (reading '0')
I am not particularly surprised that this function threw an error (and I understand why), however I need to be sure that main can safely catch the error that this function throws.
Importantly, I would really prefer to be able to catch this error at the level of main (technically there are hundreds of different implementations of doComputation in my project and having to modify all of them would be a tad difficult).
Does anyone have any ideas about how I might solve this problem? Adding a .catch to await module.doComputation does not seem to do the trick and also makes my LSP unhappy.
I tried adding a .catch statement to the offending line, and also wrapping the entire statement in a try/catch, but to no avail.
You should include a reject variable in your promise and handle the error that way
return new Promise((resolve,reject) => {
$.ajax({
type: 'GET',
url: location.href,
success: (data) => {
const text = $($.parseHTML(data)).find('#some-dom-element').text()
const total = text.match(/[+-]?\d+(\.\d+)?/g)[0]; // this line throws an error!
if (total > 0) {
resolve(total);
}
},
error: () => {
reject(-1)
},
});
});
}
And in main
function main() {
let computedValue;
module.doComputation().then((res) => {
computedValue = res
}).catch(()=>{
console.log("We caught the error!")
computedValue = null;
})
}
Hope this helps!

How to launch 1 by 1 ajax request

I'm having some trouble. I'm trying to execute my ajax function 1 by 1, not all at the same time. I'm using promise but I have no more idea on how to achieve it. Here is my code :
function run_action(action){
if(action == "login"){
return $.ajax({
url: "login.php",
type: "post",
data: {password: password},
beforeSend: function() {
console.log('beforeSend login');
},
success: function (response) {
console.log('Success Login');
},
error: function (request, error) {
console.log('Error Login');
},
})
}
if(action == "register"){
return $.ajax({
url: "register.php",
type: "post",
data: {password: password},
beforeSend: function() {
console.log('beforeSend register');
},
success: function (response) {
console.log('Success Register');
},
error: function (request, error) {
console.log('Error Register');
},
})
}
}
var actions = ['register', 'login'];
services.forEach(checkActions);
function checkActions(item, index) {
if (document.getElementById(item).checked) {
var promise = run_action(item);
promise.success(function (data) {
console.log('Run after');
});
console.log('Run first')
}
}
In this case login and register are both launched at the same time, login doesn't wait for register to finish so he can start processing.
In case you can't properly wait for checkActions from the outside, you could maintain a task queue for that:
let queue = Promise.resolve();
function checkActions(item, index) {
queue = queue
.then(() => run_action(item))
.then(() => {
console.log("Next item was processed", item);
// Your code here
});
// Synchronous code here - This won't execute in order!
}
Currently your code runs through the forEach loop with each action and invokes checkActions with that action, thus firing the request. Array.prototype.forEach executes synchronously (without any kind of check to the promises returned by $.ajax). The following would wait for 'register' to finish before firing 'login':
function checkActions(item) {
if (document.getElementById(item).checked) {
return run_action(item);
}
}
checkActions('register')
.then(data => {
return checkActions('login');
});
I'm not super familiar with jQuery's promise structure, so I used .then, but I believe you can use .success without issue as well.
Unrelated comment, your run_actions function should really be split into two functions (login and register) since they are completely unrelated aside from the fact that they are making requests.
First- its not a good practice to trust a order-based function (AKA - run them by the array order), run your functions according to logic.
For example: if the first function was failed - you dont want to run the next functions in the array.
If you consist to run the functions in array - you can use an async
async function runActions( actionsList ) {
for(const action of actionsList) {
await run_action( action );
}
};
In general - we use the then method to run anther function when specific promise is done. Like so:
promise.then( onSuccess => {
// try to log in
}, onFail => {
// tell the user the signup was failed
});
BTW - you can use the native fetch instade of jQuery ajax, and get simple to use, promise-based way to communicate with your sever.
Like so:
fetch("login.php" , {
method: 'POST', // or 'PUT'
body: {password: password}, // data can be `string` or {object}!
headers:{
'Content-Type': 'application/json'
}
}).then( ... )

How to access method from other class method that is used as a callback?

I have the following Javascript class:
class App {
log_text(text) {
console.log(text)
}
process_response(response) {
this.log_text(response) // Uncaught TypeError: this.log_text is not a function
// self.log_text(response) // Uncaught TypeError: self.log_text is not a function
}
do_stuff() {
this.log_text('hello') // OK
}
fetch_data() {
jQuery.get('http://example.com/data/sample.txt', this.process_response, 'text')
}
}
When calling the method do_stuff, I can access log_text fine by calling this.log_text. However, the method process_response, which is used as a callback handler for jQuery.get as in this example, fails because this represents a totally different object in that context.
Similarly, self.log_text also throws a TypeError.
What would be a possible (or the correct) way to call log_text from process_response as in this example?
What's happening is that you are passing your process_response function and that is all, as you've seen the context of this changes. One fix is to wrap it using arrow syntax, which will preserve the value of this when jQuery fires the callback.
fetch_data() {
jQuery.get('http://example.com/data/sample.txt', (r)=> this.process_response(r), 'text')
}
You can use Function.bind() to set the context of process_response function
fetch_data() {
jQuery.get('http://example.com/data/sample.txt', this.process_response.bind(this), 'text')
}
You can use an arrow function, which has a lexical this -
fetch_data() {
jQuery.get
( 'http://example.com/data/sample.txt'
, r => this.process_response(r)
, 'text'
)
}
Or use Function#bind which binds a context (and optionally some arguments) to a function -
fetch_data() {
jQuery.get
( 'http://example.com/data/sample.txt'
, this.process_response.bind(this)
, 'text'
)
}
Or as was done historically, preserve context with a var; this is now less preferred to the above techniques -
fetch_data() {
var ctx = this
jQuery.get
( 'http://example.com/data/sample.txt'
, function (r) { ctx.process_response(r) }
, 'text'
)
}
New JS features will improve your quality of life, however. Consider coercing your jqXHR to a Promise so you can use async and await -
const get = (opts = {}) =>
new Promise
( (resolve, reject) =>
$.get(opts)
.done((req, status, res) => resolve(res))
.fail((req, status, err) => reject(err))
)
The result is flatter code and many extraneous functions like fetch_data and process_response are no longer necessary. Even better, our minds are freed from thinking about binding functions and dynamic contexts -
class App {
log_text(text) {
console.log(text)
}
async main () {
const res = await
get ({ url: '/data/sample.txt', dataType: 'text' })
this.log_text(res)
}
}
You could even set default options for your get wrapper -
const defaultOpts =
{ dataType: 'text' }
const get = (opts = {}) =>
new Promise
( (resolve, reject) =>
$.get({ ...defaultOpts, ...opts })
.done((req, status, res) => resolve(res))
.fail((req, status, err) => reject(err))
)
Then using it -
async main () {
const res = await
get ({ url: '/data/sample.txt' })
this.log_text(res)
// ...
}

Vue resource - dynamically determine http method

I would like to dynamically determine the appropriate http method and make a single api call. However an exception is thrown when I call the method.
I expect that I am doing something wrong rather than this being a vue-resource bug. Would anyone have any advice? Thanks
For example:
let method = this.$http.post
if (this.model.id) {
method = this.$http.put
}
method(
this.url,
this.model,
options
).then(response => {
this.$router.push(this.redirect_to)
}).catch(response => {
console.log(`Error: ${response.statusText}`)
})
A javascript TypeError is thrown with message "this is not a function"
The code below works, but a bit long winded.
if (this.model.id) {
this.$http.put(
this.url,
this.model,
options
).then(response => {
this.$router.push(this.redirect_to)
}).catch(response => {
console.log(`Error: ${response.statusText}`)
})
} else {
this.$http.post(
this.url,
this.model,
options
).then(response => {
this.$router.push(this.redirect_to)
}).catch(response => {
console.log(`Error: ${response.statusText}`)
})
}
You need to bind the function to the current context.
let method = this.model.id ? this.$http.put.bind(this) : this.$http.post.bind(this)
Or just use the indexer approach.
let method = this.model.id ? 'put' : 'post'
this.$http[method](...).then(...)

Categories