I am playing around with Rxjs, observables and maps, and I found a strange behavior for Observable.throw(error) that I cannot explain.
If I have a Rx stream that uses a map operator, and I want to interrupt the process, I would have expected the method Observable.throw to be appropriate, however, this does not seem to be the case.
Consider the following example:
Rx.Observable.just("test 1").subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("done, no errors")
);
Now let's introduce an error, if I use the regular throw from Javascript it works as expected:
Rx.Observable.just("test 2").map(
x => { throw new Error(x) }
).subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("done - error was thrown, as expected")
);
Output:
Error: test 2
at Rx.Observable.just.map.x ((index):58)
at c (rx.all.compat.js:61)
at e.onNext (rx.all.compat.js:5169)
(...)
But if I use Rx.Observable.throw(...), the error callback from the following subscriber is never called, and the next callback will be called instead with some strange object that seems to be an Rx error object.
Rx.Observable.just("test 3").map(
x => Rx.Observable.throw(x)
).subscribe(
x => console.log(x),
err => console.log(err),
() => console.log("done - it does not work... why?")
);
Output:
b_subscribe: f(a)error: "test 3" scheduler: a__proto__: g
As #Whymarrh pointed out, it seems to work fine if I use a flatMap operator instead.
Documentation for map:
The Map operator applies a function of your choosing to each item
emitted by the source Observable, and returns an Observable that emits
the results of these function applications.
Documentation for Observable.throw:
Returns an observable sequence that terminates with an exception,
using the specified scheduler to send out the single onError message.
Does anyone know why when using Observable.throw inside the map operator the error callback is not called, and why the process is not interrupted?
Example in jsfiddle
I know that I can just use the regular throw and move on, I already have a working solution, I am posting this question out of curiosity to have a better understanding of how the framework works.
Quick reminder: stackoverflow has a be nice policy.
One of the comments properly answers this question:
throw new Error(x) throws an exception that simply bubbles to the top of the process.
x => Rx.Observable.throw(x) just creates a structure representing an error. To the map operator, this structure is a value like any other, to call the success handler. flatMap on the other hand will take the structure and unwrapped it, then call the error handler.
Related
Recently I needed to use RxJS. I tried to design an error handling flow, but I discovered some weird syntax passing method arguments:
.subscribe(
x => {
},
console.warn // <- Why does this compile, and warn 'is not 7' in debug console?
);
Link to minimal reproduction:
https://stackblitz.com/edit/rxjs-6-5-error-handle-no-arrow-issue
Steps to reproduce:
Use RxJS 6.5
Create a function return observable
Subscribe to observable
Pass parameter into subscribe
Just use ,console.warn, not like ,error => { console.warn(error); }
Without an arrow function, it still passes errors to console.warn. Why?
Code:
import { throwError, concat, of } from "rxjs";
import { map } from "rxjs/operators";
const result = concat(of(7), of(8));
getData(result).subscribe(
x => {
console.log("typeof(x)", typeof(x));
if (typeof(x) === 'string') {
console.log("x Error", x);
return;
}
console.log("no error", x);
},
console.warn // <- Why does this compile, and warn 'is not 7' in debug console?
);
// pretend service method
function getData(result) {
return result.pipe(
map(data => {
if (data !== 7) {
throw "is not 7";
}
return data;
})
);
}
I tried to google some keywords, js, rxjs, angular, omit arrow function, argument missing,... but I cannot locate what tech is used here.
Could anyone provide links to where this mechanism is explained?
The following two questions are related but do not explain the behavior, just say "equivalent":
The line
map(this.extractTreeData)
is the equivalent of
map(tree => this.extractTreeData(tree))
How to pass extra parameters to RxJS map operator
Why is argument missing in chained Map operator
First you need to understand what you're actually passing to the .subscribe function. Essentially it accepts three optional arguments next, error and complete. Each of it is a callback to be executed when the corresponding notification is emitted by the source observable.
So when you're using an arrow function, you define an in-place callback function.
sourceObservable.subscribe({
next: (value) => { },
error: (error) => { },
complete: () => { }
});
Instead you could define the functions separately and use it as callbacks.
onNext(value) {
}
onError(error) {
}
onComplete() {
}
sourceObservable.subscribe({
next: this.onNext,
error: this.onError,
complete: this.onComplete
});
Now this is what you're seeing. But instead of a user-defined function, you're passing the built-in console.warn() function. And in-turn the values from the notifications will be passed as arguments to the callback functions. So the value from your error is not 7 is sent as argument to console.warn() which then does it's job (i.e. prints to the console).
However there's a catch. If you wish to refer to any of the class member variables using the this keyword in the callback, it'll throw an error saying the variable is undefined. That's because this refers to the scope of the function in the callback and not the class. One way to overcome this is to use an arrow function (we've seen that already). Or use bind() function to bind the meaning of this keyword to the class.
sourceObservable.subscribe({
next: this.onNext.bind(this),
error: this.onError.bind(this),
complete: this.onComplete.bind(this)
});
So if you wish to only have the error callback for example, you could explicitly state it and ignore the others.
sourceObservable.subscribe({ error: console.warn });
Now as to your question "why no parentheses in the function call", it was discussed here and here. The arguments expect a reference to a function and the function names denotes their reference.
console.log is a function
function can be called with arguments in a bracket
console.log("123") means call function console.log with argument "123"
tree => console.log(tree) is also a function
it can also be called with arguments in a bracket, eg.
(tree => console.log(tree))(tree)
so a function with callback as its argument can call its callback with arguments in a bracket
function example(callback) {
callback();
}
so if we pass console.log to it, example(console.log), it basically runs as
function example(callback) {
console.log();
}
if we pass tree => console.log(tree) to it, example(tree => console.log(tree)), it basically runs as
function example(callback) {
(tree => console.log(tree))();
}
if you understood above code. it's easy to understand subscribe now.
function subscribe(nextCb, errorCb, completeCb) {
// ... got next data
nextCb(data);
//... got error
errorCb(error);
// completed observe
completeCb();
}
so your error callback console.log basically get called as console.log(error);
error=> console.log(error) basically get called as (error=> console.log(error))(error);
which in this case results are same.
In JS functions are first class objects. When you have the code console.warn no brackets you have a reference to this object but you're not invoking that object, that would require braces console.warn(). For example you can do:
let x = console.warn;
console.log('not happened yet');
x('test');
So your code is simple passing the console.warn function to the parameter of the Subscribe failure in exactly the same manner as you might pass any other function, e.g.
Subscribe(() => {}, () => {});
[why] show Warn 'is not 7'
The other part of this is that your throwing an error throw "is not 7";. The signature of the error call of Subscribe is thus:
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): Subscription;
So the parameter of error is of type any. So the throw passes an Error to the error function handler. This is set as console.warn which has a signature of :
console.warn(obj1 [, obj2, ..., objN]);
console.warn essentially turns whatever parameter it's passed into a string, JS is not strongly typed and this is essentially down to type coercion, and logs it. the string of throw "is not 7"; is is not 7. So it logs is not 7.
All in all I'd say this is all all a bit cryptic and potentially difficult to follow. There is nothing technically wrong here, but I would say it would make more sense to do the following:
.subscribe(
x => {
},
x => {console.warn(x);}
);
Based on the principle that "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
This happens due to the three possible types of value an Observable is able to emit,
next
error
complete
These logic is translated in the arguments of the subscription function, so the first function callback will trigger the values emitted via next, the second callback the values emitted with error and the third function with the complete value.
In you case, console.warn is passed to the second function as a function that will be called each time an error is emitted.
For the second question you can refer to the arrow function docs, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Your subscribe expects 2 arguments. The first argument is a function that will be called with the "next" values and the second argument is again a function that will be called with if there occured an error. So, since "console.warn" is a function, you can just use it as a second argument.
(error) => console.warn(error) is the same as console.warn(error)
But be careful if since console.warn does not rely on the context "this", you will give no issues. But if you want to use a function that uses the context "this", you will need to use an arrow function.
For more info about JS scoping: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
I have the following code to run 15 promises sequentially:
[func1, func2, func3, ...etc].reduce((prev, curr) =>
prev.then(curr).catch(console.error)
, Promise.resolve())
The problem is: When a error happens inside of one of the functions, the following error popups in my terminal:
Cannot read property 'reduce' of undefined
I've searched, but wasn't able to find someone with the same error. Is there a workaround?
Thank you.
My crystal ball says that inside of one of these functions you are using reduce on the array that you receive as an argument, as the result of the previous function.
The problem with that is when an error happens, your .catch(console.error) does handle it immediately and returns a promise that is fulfilled with undefined (the return value of console.error(…)). This result is then passed to the next function, which is executed regardless of the error. You'll want to handle the error in the end only:
[func1, func2, func3, …].reduce((prev, curr) =>
prev.then(curr)
, Promise.resolve()).catch(console.error)
I was wondering whether there's a way to run a function in JavaScript and let the program ignore it if there's an error running the function?
player.play('./sounds/throughQueue.mp3', function(err){
if (err) throw err
})
Pretty much like this without the "throw err".
The program should just continue.
Looking forward to your answers.
As you mentioned in comments you want to know how to handle async function error;
You could do something like this:
function myAsyncFunction(){
return new Promise((resolve, reject) => {
// do some async anction that returns error
foo.asyncCall((err, item) => err && reject(err))
}
}
myAsyncFunction().catch(e => console.error(e))
If you observe code above async error was handled in two styles:
Callback function usually has 1st arg returning error and you can do if (err) and add any logic you want in there. Or just ignore it.
Within Promise you can use reject which is second argument in constructor. And when you reject error, you can use .catch method upon that Promise and use any custom logic you want, either new promise call or function callback.
Note I used console.error within callback function, you could just do nothing there and it will be ignored (your program will continue)
Yes, you can ignore callback/promise error or swallow try/catch caught error, but it can only make the execution go on, not make the behavior or result correctness.
If in the callback function, you can ignore error with empty error handling and the execution won't break either, but the behavior is still dependent on if there is error in the invocation.
If in promise then, you can also ignore errors caught.
If in async/await way, you must try/catch the invocation and swallow the caught err.
In a word, I suggest you handle the possible errors. after all, quality software is what we want. and what maybe happen will happen eventually.
I am trying to map each item of a stream to a promise, something like this
myStream$
.flatMap(id => Rx.Observable.fromPromise(database.get(id)))
.subscribe(val => console.log(val));
myStream$ has about 15 items. Since none of these items can be found in the database, each promise will be rejected. I was expecting 15 log outputs printing an error. However all I get is one single error
rx.js:77 Uncaught {"status":404,"name":"not_found","message":"missing","reason":"missing"}
Why am I only getting one error instead of 15?
This behavior is expected - whenever an error is thrown, the stream will finalize (in other words: "stop & unsubscribe all subscribers").
If you want your stream to complete properly regardless of one or all db-request failing, you have to handle the error inside the sub-stream that wraps the promise, since a reject will be evaluated as an error in RxJS.
Rx.Observable.from([1,2,3,4,5])
.flatMap(
id => mockRequest(id)
.catch(error => {
console.error(error);
return Rx.Observable.empty(); // here we just return an empty stream, so the "main"-stream won't receive the error and continue with the other ids in the queue
})
)
.subscribe(
val => console.log(val),
error => console.error("Stream hit an error and will finalize", error),
complete => console.log("Done!")
);
function mockRequest(id) {
return Rx.Observable.throw("Request failed for: " + id);
}
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
Note: Instead of return Rx.Observable.empty(); you can of course return any fallback-value via Observable.of("myFallbackValue")
Sidenote & suggestion: You'll have it easier when you make your rest-calls directly with RxJS and not have to wrap a promise. (though technically both are perfectly valid ways)
With promises, we can use a variant of .then to split up the chain when an error occurs. Here is an example using fetch
fetch('http://website.com').then(
// Perform some logic
(response) => response.json().then(({ answer }) => `Your answer: ${answer}`),
// Skip json parsing when an error occurs
(error) => 'An error occurred :(',
).then(console.log);
This allows me to skip the response processing logic and only respond to errors that were raised in the original fetch statement. Something similar in RxJS might look like this:
Observable.fromPromise(fetch('http://website.com'))
// if I put .catch here, the result will be piped into flatMap and map
.flatMap(response => response.json())
.map(({ answer }) => `Your answer: ${answer}`)
// if I put .catch here, errors thrown in flatMap and map will also be caught
.subscribe(console.log);
As the comments in the code state, I can't simply put a catch operator in as it doesn't have the same behaviour as my promise chain.
I know I can get it with custom operators involving materialising, or merging an error catching observable with this one, but it all seems like pretty major overkill. Is there a simple way to achieve the promise chain behaviour?
Actually, if I was in your situation I wouldn't be worried about catching errors from flatMap and map. When the source Observable throws an error then it'll be propagated to the observer. So I'd just use an error handler when calling subscribe (otherwise the error is rethrown):
.subscribe(console.log, err => console.log('error:', err));
Note that when an error occurs in the source Observable (a Promise in your case) that it's propagated as error notifications, not as standard next notifications. This means that flatMap() and map() won't have absolutely any effect on the error message. If you used catch() or materialize() that both operators (flatMap and map) would have to be able to handle this type of data (and not throw yet another error).
Anyway you can always use share() or publish() and make two different subscriptions where each handles only one type of signals:
let source = Observable.fromPromise(fetch('http://website.com')).publish();
source
.subscribe(undefined, err => console.log(err));
source
.flatMap(...)
.map(...)
.subscribe(console.log, () => {});
source.connect();
Now I have a separate observer only for errors.
Note that I had to make an empty callback with () => {} so the error will be silently ignored. Also note that when using multicasting (the publish() operator) then the Subject inside might have some specific behavior I should be aware of but maybe it doesn't matter in your use-case.