CloudL
CloudL

Reputation: 211

How to use predicates in an async function?

I use thirtyfour in my Rust script, and it uses tokio as the async runtime.

When I use find in a Vec::iter, it doesn't work as I expect:

#[tokio::main]
async fn main() -> WebDriverResult<()> {
    let dropdown = driver.find_element(By::Tag("select")).await?;
    dropdown
        .find_elements(By::Tag("option"))
        .await?
        .iter()
        .find(|&&x| x.text() == book_time.date) // Error, x.text() return a futures::Future type while book_time.date return a &str.
        .click()
        .await?;
}

After I tried Ibraheem Ahmed's solution, I met more errors:

let dropdown = driver.find_element(By::Tag("select")).await?;
let elements = dropdown.find_elements(By::Tag("option")).await?;
let stream = stream::iter(elements);
let elements = stream.filter(|x| async move { x.text().await.unwrap() == target_date });
error: lifetime may not live long enough
   --> src\main.rs:125:38
    |
125 |     let elements = stream.filter(|x| async move { x.text().await.unwrap() == target_date });
    |                                   -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
    |                                   ||
    |                                   |return type of closure is impl futures::Future
    |                                   has type `&'1 thirtyfour::WebElement<'_>`

Upvotes: 3

Views: 1143

Answers (2)

Ultrasaurus
Ultrasaurus

Reputation: 3169

There's a good thread on the Rust User's Forum that covers a similar question.

I tried the code snippets below by modifying the thirtyfour tokio_async example. I didn't have your full context, so I created an example that found a link based on its text on the wikipedia.org home page.

  1. filter_map.

    let target_value = "Terms of Use";
    let found_elements: Vec<_> = stream
        .filter_map(|x| async move {
            if let Ok(text) = x.text().await {
                if text == target_value {
                    println!("found");
                    return Some(x);
                }
            }
            None
        })
        .collect()
        .await;
    
  2. while loop (which is probably not what you are after, but could be a simple solution if your logic fits easily inside)...

    while let Some(element) = stream.next().await {
        let text = element.text().await?;
        println!("link text result: {}", text);
    }
    

Upvotes: 1

Ibraheem Ahmed
Ibraheem Ahmed

Reputation: 13538

You can use a Stream, which is the asynchronous version of Iterator:

use futures::stream::{self, StreamExt};

fn main() {
    // ...
    let stream = stream::iter(elements).await?;
    let elements = stream
        .filter(|x| async move { x.text().await.as_deref() == Ok(book_time.date) })
        .next()
        .click()
        .await?;
}

Upvotes: 0

Related Questions