Rust: Async find (use await within predicate) - rust

I am trying to use async within find predicates.
We can use the find method to look up an element within an iterator as follows:
let element = some_elements.iter().find(|element| {
// some_async_method(element).await;
// true or false based on some logic
});
In my case, I need to call async methods as a part of my lookup logic. However, when I use await, I end up with the error that await cannot be called within non-async block, which is expected. When I try to make my function async as follows:
let element = some_elements.iter().find(|element| async move{
// some_async_method(element).await;
// true or false based on some logic
});
I get the error: expected bool, found opaque type
I tried to to use rayon, but could not find an example for what I am looking for.

You can use futures::stream::iter to turn your iterator into a Stream.
Since find is not implemented on StreamExt I replaced it with filter().next() in the following:
use futures::stream::StreamExt;
//…
let element = futures::stream::iter(&some_elements)
.filter(|e| Box::pin(async move { predicate(e).await }))
.next()
.await;
If you actually want to do your work asynchronously though you'd have to be a little more verbose and collect into a FuturesOrdered/FuturesUnordered depending on your actual requirements:
let element = some_elements
.iter()
.map(|&e| async move { (predicate(e).await, e) })
.collect::<FuturesUnordered<_>>()
.filter_map(|(p, e)| Box::pin(async move { p.then_some(e) }))
.next()
.await;

Related

How to properly handle a tokio::try_join! if one of the tasks panics and cleanly abort?

For the two async functions that I am passing to my try_join!(), let's say there's 3 ways that they could panic.
I'm trying to use a set_hook to catch the errors but I'm not sure how to do a match statement on the panics so I can display a custom error message for each of the ways that they can panic. It looks like set_hook takes a Box(Any) (?), so I was wondering if there was a way to check the type of Error. Basically I just don't want to do regex on the ErrString.
I'm also not sure what the best way to abort the runtime within each match branch. I'm currently using std::process::exit(0).
code looks like:
set_hook(Box::new(|panic_info| {
println!("Thread panicked! {}", panic_info);
// std::process::exit(0);
}));
let (result1, result2) = tokio::try_join!(func1, func2); // code that could panic
I want to be able to do something like
set_hook(Box::new(|panic_info| {
match panic_info {
panic_type_1 => { println!("X was invalid, please try using valid X") }
panic_type_2 => { println!("Y was invalid, please try using valid Y") }
panic_type_3 => { println!("Z was invalid, please try using valid Z") }
_ => { println!("Something else happened: {}", panic_info) }
}
}));
let (result1, result2) = tokio::try_join!(func1, func2); // code that could panic
Don't bother with set_hook. The future that tokio::task::spawn* returns resolves to a Result with a JoinError type, which has a [try_]into_panic to get the boxed object that was passed to panic.
The panic message is stored as a Box<dyn Any> which has tons of methods for downcasting it into various types.

How to log error and return/continue a result/option

I have come across many instances in that I need to write a code similar to this snippet. I was wondering if there's a shorter way to do it?
loop {
let res = match do() {
Ok(res) => res,
Err(e) => {
eprintln!("Error: {}", e);
continue;
}
}
// Do stuff with `res` ...
}
or
fn some_fn() {
let res = match do() {
Some(res) => res,
None => {
eprintln!("Error: not found");
return;
}
}
// Do stuff with `res` ...
}
I was looking for something like the ? keyword to return early with errors but in a case where the function returns nothing and I just want to return nothing if the result is None/Error.
Maybe something like this:
loop {
do().unwrap_or_log(|e| eprintln("{}", e).continue // :D
}
And consider do() is never gonna be this short. It's probably a chain of a few function calls which is already too long.
Maybe the way I'm doing it is the only way or maybe I'm doing something wrong which makes to do this and I shouldn't be doing it!?
This is pretty much how it is. However nice the many chaining functions are, they cannot affect the control flow where they are used.
One suggestion I may make: if you have many fallible operations that need to be logged and continued in an infallible context, you could move those into a single fallible function that you then can log and skip any errors all at once.
You aren't the only person to have complaints though and others have suggested changes to make this flow less bothersome.
There's the let else proposal, which I believe is implemented in the nightly compiler. It just needs to be documented and stabilized. It would look like this (playground):
let Ok(res) = do_this() else { continue };
Or perhaps a postfix macro proposal could be implemented eventually, which may look like this:
let res = do_this().unwrap_or!{ continue };
I'll note though that neither of these give access to the Err(e) value. So you'd still need a custom method for logging.

Run 3 async task indepedently in infinite loop

I have 3 different async methods say func1, func2, func3 on a struct object.
What I want to accomplish is something like:
loop {
obj.func1().await;
set_timeout(Duration::from_secs(5)).await;
}
loop {
obj.func2().await;
set_timeout(Duration::from_secs(5)).await;
}
loop {
obj.func3().await;
set_timeout(Duration::from_secs(5)).await;
}
I want all these 3 loops to run in parallel. Obviously, in this form it won't work because the 2nd and 3rd loops will be unreachable.
I've thought of the below solution:
loop {
thread::spawn(move || async move {
obj.func1().await;
obj.func2().await;
obj.func3().await;
set_timeout(Duration::from_secs(5)).await;
});
}
But it has 2 issues:
My struct does not implement the Copy trait (using some 3rd party crates, so cannot do much there).
As every function call will run as a seperate thread here, I doubt the timeout will work properly here!
How should I approach this?
You can use an async block to create a new future, so if you wrap each loop in an async block you'll get three futures. Then you can use the join! macro to await them all simultaneously:
let fut1 = async {
loop {
obj.func1().await;
set_timeout(Duration::from_secs(5)).await;
}
};
let fut2 = async {
loop {
obj.func2().await;
set_timeout(Duration::from_secs(5)).await;
}
};
let fut3 = async {
loop {
obj.func3().await;
set_timeout(Duration::from_secs(5)).await;
}
};
join!(fut1, fut2, fut3);
The join! macro will drive the futures.
Alternatively, your async runtime likely has a way to submit a future as a new independent task, such as tokio::spawn.

How to call an async JavaScript Import Function from WebAssembly (Rust) in a node.js environment?

Let's consider an example import object that looks like this:
const importObject = {
exampleAsyncImportFunction: async () => fs.readFile("./someExampleFile.txt", "utf-8") // returns Promise<String>
};
I want to use it in my rust-written WASM-Module similar to this:
#[wasm_bindgen]
extern "C" {
pub async fn exampleAsyncImportFunction() -> JsValue; // Importing the JS-Function
}
#[wasm_bindgen]
pub async fn exampleExportFunction() -> Result<JsValue, JsValue> {
let theJSPromise = exampleAsyncImportFunction(); // Call the async import function
let promiseResult = theJSPromise.await; // Execute the async import function
// Do sth with the result
OK(JsValue::UNDEFINED)
}
Unfortunately, this leads to an unreachable error. Interestingly, it does work, when the JavaScript-Function returns e.g. a string, like this:
const importObject = {
exampleAsyncImportFunction: async () => "Some dummy content" // returns Promise<String> as well
};
But of course, an async import function should perform some actual asynchronous tasks, otherwise it would be better to use a synchronous function.
I tried to do some research and found 'js_sys::Promise' which represents a JS-Promise in Rust, and 'wasm_bindgen_futures::JsFuture' which enables to somehow convert a JS-Promise to a Rust-Future. But I don't understand, if/how these types may help with the problem and also how they should be used in general in this context.
Also it seems, that the return type of the JS-Import-Function, which is 'JsValue' by declaration, somehow implements the 'Future'-Trait. I understand that due to that, 'awaiting' the result is possible. But I'm confused, what's the actual underlying type (JsFuture, Promise, something else...).
I hope that someone can help me with this, by solving the actual problem, but also explaining a bit the relationships between all the types (especially regarding JsValue).
Thank you in advance!
I found the solution myself, and as there seems to be no good information elsewhere in the internet, I want to share my solution for anybody, who has similar issues.
For importing the JavaScript-Function, it is important to act like the function was synchronous, although it is asynchronous. The reason for that is, that async functions in Rust are only executed, when an executor or await is used. Therefore, the async-JS-Function, which under the hood is a synchronous function that returns a Promise can be used synchronously. The returned promise can then be called explicitly.
The import section will look like the following:
#[wasm_bindgen(module = "/some/file.js")]
extern "C" {
pub fn exampleAsyncImportFunction() -> JsValue; // JsValue <==> Promise
// Instead of:
// pub async fn exampleAsyncImportFunction() -> Result<JsValue, JsValue>
}
To consume the import function, the Promise as a JsValue has to be converted into a js_sys::Promise and then to a wasm_bindgen_futures::JsFuture. After that it can be awaited as expected.
The sample export function from the original questions will look like this:
#[wasm_bindgen]
pub async fn exampleExportFunction() {
// Get a reference to the import function as a 'JsValue'
let promiseAsJsValue = exampleAsyncImportFunction(); // No execution, yet
// Convert 'JsValue' to 'js_sys::Promise', which is a proxy-type for JS-Promises
let promise = js_sys::Promise::from(promiseAsJsValue);
// Convert 'js_sys::Promise' to 'wasm_bindgen_future::JsFuture'
let future = wasm_bindgen_future::JsFuture::from(promise);
// Actually execute the Promise/Future
let result: Result<JsValue, JsValue> = future.await;
// Interpret the result
if let Ok(content) = result {
// do sth with the content
}
}
I hope that this approach, specifically the transformation from JsValue to JsFuture, will help some of you, as the official docs don't cover that use case, but only the manual instantiation of a js_sys::Promise (e.g. Promise::resolve(&"foo".into()) ) or using web_sys::window::fetch().

expected `()`, found opaque type for async function

I am following a guide for setting up a WebRTC data-channel with web-sys. I can copy and paste the code and it compiles correctly. The start() function is async which makes it possible to await a JsFuture inside the main scope, however I am trying to move this await to the onmessage_callback block instead. Just by adding this one line to the original implementation I have this:
let onmessage_callback =
Closure::wrap(
Box::new(move |ev: MessageEvent| {
let desc = JsFuture::from(pc1.create_offer()).await.unwrap();
match ev.data().as_string() {
Some(message) => {
console_warn!("{:?}", message);
dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
}
None => {}
}
}) as Box<dyn FnMut(MessageEvent)>,
);
dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();
Once I compile this I ofcourse get an error saying that awaiting the future desc is only possible inside an asynchronous context. I figured that I could add the async keyword when defining the FnMut function:
let onmessage_callback =
Closure::wrap(
Box::new(move |ev: MessageEvent| async { // <-- async
let desc = JsFuture::from(pc1.create_offer()).await.unwrap();
match ev.data().as_string() {
Some(message) => {
console_warn!("{:?}", message);
dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
}
None => {}
}
}) as Box<dyn FnMut(MessageEvent)>,
);
dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();
But when I compile this I get the following error:
error[E0271]: type mismatch resolving `<[closure#src/lib.rs:48:22: 57:14] as FnOnce<(MessageEvent,)>>::Output == ()`
--> src/lib.rs:48:13
|
48 | / Box::new(move |ev: MessageEvent| async {
49 | | let desc = JsFuture::from(pc1.create_offer()).await.unwrap();
50 | | match ev.data().as_string() {
51 | | Some(message) => {
... |
56 | | }
57 | | }) as Box<dyn FnMut(MessageEvent)>,
| |______________^ expected `()`, found opaque type
|
= note: expected unit type `()`
found opaque type `impl Future<Output = [async output]>`
= note: required for the cast to the object type `dyn FnMut(MessageEvent)`
I am not sure how to proceed with this, I think the error is saying that the callback returns a future but it should be void instead.
How do I define an async callback?
You declare that your function will not return anything by saying
Box<dyn FnMut(MessageEvent)>
//which is the same thing as Box<dyn FnMut(MessageEvent)->()>
then in your callback, you actually return an async block back.
I am not familiar with the library in question but looking at the documentation you are not supposed to pass an async function.
if you must use async and if you have a runtime running already you can spawn a task inside a sync block.
But in javascript world you cant really wait for something if something is async you can only give it a callback (async/await keywords exists but all they do is to return you another promise)
So I believe you should be using the promise API here and provide a callback for the then method.
It is not pretty but this is how things get done in the js world and what you are facing here is called the callback hell in js which is a thing :D
Ps:
Javascript has a single threaded runtime and things get scheduled on a forever running loop called the event loop. Every time you pass a callback somewhere at some point it gets registered on the event loop and gets called eventually.
What's important to understand is that the calling part of it is not guaranteed and if you were to block event loop in one of your callbacks then next tick of the loop would never come therefore the entire runtime would lock up. this is the reason behind the design decisions that lead to the callback hell
Pps:
I don't really think you should bundle (if even its possible) a rust async runtime with your wasm binary since js has its own as I mentioned just stick with the promise api
The reason is as specified by #nikoss, you pass a future-returning function and cast it to a unit-returning function.
As for how to solve that, you can spawn the future on JS promise microtasks queue with spawn_local():
let (pc1_clone, dc1_clone) = (pc1.clone(), dc1.clone());
let onmessage_callback =
Closure::wrap(
Box::new(move |ev: MessageEvent| {
wasm_bindgen_futures::spawn_local(async move {
let desc = JsFuture::from(pc1_clone.create_offer()).await.unwrap();
match ev.data().as_string() {
Some(message) => {
console_warn!("{:?}", message);
dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
}
None => {}
}
});
}) as Box<dyn FnMut(MessageEvent)>,
);
dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget();

Resources