I want to test every value an observable emits, and if it fits certain criteria, then error-out the result, otherwise pass the value on. Is there an operator for this?
If you use throw, you can separate cases that do not match the condition, but they will terminate immediately.
let source = Observable.of(1,2,3);
source
.mergeMap(value => {
if (value > 1) { // condition
return Observable.throw(`Out Of Condition: ${value}`);
}
return Observable.of(value);
})
.subscribe(
value => console.log(`Next: ${value}`),
error => console.log(`Error: ${error}`),
() => console.log('completed')
);
Result:
Next: 1
Error: Out Of Condition: 2
You can also think of ways like stdout and stderr in bash. For this, additional information was augmented. In this case, it does not end in the middle.
const stdout = 1;
const stderr = 2;
let source = Observable.of(1,2,3);
source
.map(value => {
if (value > 1) { // condition
return [stderr, value];
}
return [stdout, value];
})
.subscribe(channel_value => {
let channel = channel_value[0];
let value = channel_value[1];
if (channel == stdout) {
console.log(`stdout: ${value}`);
}
else if (channel == stderr) {
console.log(`stderr: ${value}`);
}
});
Result:
stdout: 1
stderr: 2
stderr: 3
I don't know if this is the right answer, but what worked for me, was Observable.create
Observable.create((observer: Observer) =>
sourceObservable.subscribe((val: Value) => {
if(condition)
observer.next(val);
else
observer.error(val);
}, observer.error, observer.complete)
)
This is very inelegant. I hope there is a better way
Related
There are some situations where stderr is not available. Is there a way to have dbg!() print to stdout or an alternative command that prints the same information as dbg!() to stdout?
For an alternative command, copy-and-paste the implementation of dbg, changing eprintln to println and $crate to ::std:
macro_rules! dbg {
// NOTE: We cannot use `concat!` to make a static string as a format argument
// of `println!` because `file!` could contain a `{` or
// `$val` expression could be a block (`{ .. }`), in which case the `println!`
// will be malformed.
() => {
::std::println!("[{}:{}]", ::std::file!(), ::std::line!())
};
($val:expr $(,)?) => {
// Use of `match` here is intentional because it affects the lifetimes
// of temporaries - https://stackoverflow.com/a/48732525/1063961
match $val {
tmp => {
::std::println!("[{}:{}] {} = {:#?}",
::std::file!(), ::std::line!(), ::std::stringify!($val), &tmp);
tmp
}
}
};
($($val:expr),+ $(,)?) => {
($(::std::dbg!($val)),+,)
};
}
fn main() {
dbg!(1 + 1);
}
Not sure why i'm getting this.. any suggestions would be grateful!
I ran into issues with my original coding where I had Firebase pod and Firebase Package.. so I started from scratch since that wasnt fixing itself.. now I get this.. and I am at a loss for how to resolve it.
static func fetchUsers() -> AnyPublisher<[UserProfile], Error> {
Future< [UserProfile], Error > { promise in
self.db.collection("Users")
.getDocuments { (snapshot, error) in
if let error = error {
promise(.failure(error))
return
}
guard let snapshot = snapshot else {
promise(.failure(FirebaseError.badSnapshot))
return
}
var users = [UserProfile]()
snapshot.documents.forEach { document in
print(users.count)
if let user = try? document.data(as: UserProfile.self){
if users.contains(where: { $0.id == user.id}) {return}
users.append(user)
} else {
print("Not working")
}
}
promise(.success(users))
}
}
.eraseToAnyPublisher()
}
I believe this is the syntax you're after:
var users = [UserProfile]()
users = snapshot.documents.compactMap { (document) -> UserProfile? in
if users.contains(where: { $0.id == user.id}) {
return nil
} else {
return try? document.data(as: UserProfile.self)
}
}
Also be aware that when you iterate something in Swift and encounter a false condition on an iteration, return will return out of the greater scope, not just that iteration. Therefore, use continue.
for x in y {
guard x > 0 else {
continue // continues loop
}
// ...
}
I wrote a function that is pipe-able:
HandleHttpBasicError<T>()
{
return ((source:Observable<T>) => {
return source.pipe(
catchError((err:any) => {
let msg = '';
if(err && err instanceof HttpErrorResponse)
{
if(err.status == 0)
msg += "The server didn't respond";
}
throw {
err,
msg
} as CustomError
})
)
})
}
I can use this function this way in my HttpService:
checkExist(id:string)
{
return this.http.head<void>(environment.apiUrl + 'some_url/' + id)
.pipe(
HandleHttpBasicError(),
catchError((err:CustomError) => {
if(err.msg)
throw err.msg;
if(err.err.status == HttpStatusCodes.NOT_FOUND)
throw("It doesn't exist.");
throw(err);
})
)
}
It's working great. When I subscribe to checkExist(), I get a good error message, because HandleHttpBasicError first catches an error and throws it to the service's catchError(), which throwes the error message because it was not null.
This way, it allows me to have a global catchError() which handles error messages that will always be the same. In the future, I will do it in a HttpHandler, but that's not the point here.
Is it ok to chain the errors with the throw keyword?
I tried to return Observable.throwError(), but the browser said
Observable.throwError is not a function
My imports are import {Observable, of, throwError} from 'rxjs';.
Isn't it better to do this:
return ((source:Observable<T>) => {
return source.pipe(
catchError((err:any) => {
msg = '';
...
return of({err, msg} as CustomError)
/* instead of
throw(err)
-or-
return Observable.throwError(err) (which doesn't work)
*/
})
)
})
?
Is it ok to chain the errors with the throw keyword ?
Yes, it's totally fine. rxjs try-catches such cases and converts it into an error notification.
I tryed to return Observable.throwError() but the browser say "Observable.throwError is not a function"
With rxjs6, the Observable prototype is no longer modified to contain operators or these »creation operators«, instead they are exposed as standalone functions. You can read more about it here, but the gist of it is that you'd just return throwError(…), e.g.
return source$.pipe(
catchError(err => err.code === 404
? throwError("Not found")
: throwError(err)
)
)
I read that using unwrap on a Result is not a good practice in Rust and that it's better to use pattern matching so any error that occurred can be handled appropriately.
I get the point, but consider this snippet that reads a directory and prints the accessed time for each entry:
use std::fs;
use std::path::Path;
fn main() {
let path = Path::new(".");
match fs::read_dir(&path) {
Ok(entries) => {
for entry in entries {
match entry {
Ok(ent) => {
match ent.metadata() {
Ok(meta) => {
match meta.accessed() {
Ok(time) => {
println!("{:?}", time);
},
Err(_) => panic!("will be handled")
}
},
Err(_) => panic!("will be handled")
}
},
Err(_) => panic!("will be handled")
}
}
},
Err(_) => panic!("will be handled")
}
}
I want to handle every possible error in the code above (the panic macro is just a placeholder). While the code above works, I think it's ugly. What is the idiomatic way to handle a case like this?
I read that using unwrap on a Result is not a good practice in Rust.
It's not that easy. For example, read my answer here to learn a bit more. Now to your main problem:
Reduce right shift by passing Ok value to the outside
One big issue with your code is the right shift: for example, the meta.accessed() call is indented a whole lot. We can avoid this by passing the value we want to work with out of the match:
let entries = match fs::read_dir(&path) {
Ok(entries) => entries, // "return" from match
Err(_) => panic!("will be handled"),
};
for entry in entries { // no indentation! :)
// ...
}
That's already a very good way to make the code more readable.
Using the ? operator to pass the error to the calling function
Your function could return a Result<_, _> type in order to pass the error to the calling function (yes, even main() can return Result). In this case you can use the ? operator:
use std::{fs, io};
fn main() -> io::Result<()> {
for entry in fs::read_dir(".")? {
println!("{:?}", entry?.metadata()?.accessed()?);
}
Ok(())
}
Use helper methods of Result
There are also many helper methods, like map() or and_then(), for the Result type. and_then is helpful if you want to do something, if the result is Ok and this something will return a result of the same type. Here is your code with and_then() and manual handling of the error:
fn main() {
let path = Path::new(".");
let result = fs::read_dir(&path).and_then(|entries| {
for entry in entries {
let time = entry?.metadata()?.accessed()?;
println!("{:?}", time);
}
Ok(())
});
if let Err(e) = result {
panic!("will be handled");
}
}
There really isn't only one way to do this kind of error handling. You have to get to know all the tools you can use and then need to choose the best for your situation. However, in most situations, the ? operator is the right tool.
Result happens to have a lot of convenience methods for these kinds of things:
use std::fs;
use std::path::Path;
fn main() {
let path = Path::new(".");
match fs::read_dir(&path) {
Ok(entries) => {
for entry in entries {
match entry.and_then(|e| e.metadata()).map(|m| m.accessed()) {
Ok(time) => {
println!("{:?}", time);
},
Err(_) => panic!("will be handled")
}
}
},
Err(_) => panic!("will be handled")
}
}
And usually you will not have so much logic in main and will simply be able to use ? or try! in another function:
use std::fs;
use std::path::Path;
fn print_filetimes(path: &Path) -> Result<(), std::io::Error> {
for entry in fs::read_dir(&path)? {
let time = entry.and_then(|e| e.metadata()).map(|m| m.accessed())?;
println!("{:?}", time);
}
Ok(())
}
fn main() {
let path = Path::new(".");
match print_filetimes(path) {
Ok(()) => (),
Err(_) => panic!("will be handled"),
}
}
I'm diving into rust, and I'm trying to do something like this:
match send("select * from User;") {
ConnError => println!("Connection error!"),
DBError(e) => println!("Database error {}", e),
Ok(response) => {
...
}
}
and I'm trying to figure out a compact way of defining the send function. I saw the Result enum, but it only handles one kind of error at a time. I was hoping that I could define my own enum like this:
fn send(query: str) -> enum { Ok(Box<Response>), ConnError, DBError(str) } {
...
}
alas, it is not possible, it's complaining about the unexpected 'enum' keyword. Is there any way to do what I'm trying here, or perhaps make Result handle multiple error types? Thanks!
As you say, you can use Result but you have to define the enum with your error types separately, as you can't define it directly in the return of your function.
Something like this:
use std::rand::distributions::{IndependentSample, Range};
fn main() {
match send("select * from foo") {
Ok(Response) => println!("response"),
Err(e) => match e {
ConnError => println!("connection error"),
DbError(err) => println!("{}", err)
}
}
}
// the enum with your errors
enum DataLayerError {
ConnError,
DbError(String)
}
struct Response; /*...*/
fn send(_query: &str) -> Result<Response, DataLayerError> {
let between = Range::new(0u, 2);
let mut rng = std::rand::task_rng();
// return a random result
match between.ind_sample(&mut rng) {
0 => Ok(Response),
1 => Err(DbError("yikes".to_string())),
2 => Err(ConnError),
_ => unreachable!()
}
}