rohan
rohan

Reputation: 335

How do I send request without await?

I am using reqwest, and am trying to send a request every 97 ms. But, I do not want to have to wait for the last request to happen or for it to be read.

I just want a request to send every 97 ms and to be sending the output at all times to stdout.

my (current) code is like this: (keys is an array with api keys)

    loop {
    for i in keys.iter() {
        let url = format!("https://api.[insertsite].com/blahblah&apiKey={}", i);
        let res = client
            .get(url)
            .send()
            .await?
            .text()
            .await?;

        sleep(Duration::from_millis(97)).await;

        println!("{:?}", res);
        }
}

If I remove the awaits the compiler tells me that error[E0599]: no method named `text` found for opaque type `impl std::future::Future` in the current scope.

TL;DR: I want to send a get request every 97 ms, regardless of any response. If/when there is a response pipe that response to stdout.

EDIT:

I tried to use threads, but I do not really know how to. Here is what I came up with:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let keys = ["key", "key", "key"];
    let client = reqwest::Client::builder()
        .build()?;
    for i in keys.iter() {
        tokio::spawn(async move {
            let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
            let res = client
                .get(url)
                .send()
                .await
                .text()
                .await;
             println!("{:?}", res);
             });
            sleep(Duration::from_millis(97)).await;
}
}

I get this error when trying to run:

error[E0599]: no method named `text` found for enum `std::result::Result<reqwest::Response, reqwest::Error>` in the current scope
  --> src/main.rs:22:18
   |
22 |                 .text()
   |                  ^^^^ method not found in `std::result::Result<reqwest::Response, reqwest::Error>`

Upvotes: 2

Views: 3466

Answers (1)

Cerberus
Cerberus

Reputation: 10217

You have already discovered that tokio::spawn is the correct tool for this task, since you essentially want to use some async code in a "fire-and-forget" way, without waiting for it in the main program flow. You just need a little adjustments to your code.

First, for the error you're quoting - it is due to the fact that you have to somehow handle the possible error during the request. You can simply add question marks after each await returning Result, but then you run into the following:

error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `Try`)
  --> src/main.rs:11:23
   |
9  |           tokio::spawn(async move {
   |  _________________________________-
10 | |             let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
11 | |             let res = client.get(url).send().await?.text().await?;
   | |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in an async block that returns `()`
12 | |             println!("{:?}", res);
13 | |         });
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `Try` is not implemented for `()`
   = note: required by `from_error`

The "this function" bit might be a little misleading, since the rest of the error is talking about "async block", but the gist is simple: if you use question mark to bubble error out of somewhere, the happy path must return Result, too. Well, let's simply add Ok(()) to the end of async block, and here's the next error:

error[E0282]: type annotations needed
  --> src/main.rs:11:51
   |
11 |             let res = client.get(url).send().await?.text().await?;
   |                                                   ^ cannot infer type of error for `?` operator
   |
   = note: `?` implicitly converts the error value into a type implementing `From<reqwest::Error>`

That's expected, too - in the ordinary function return type would be provided by its signature, but in the async block that's not an option. We can, however, use the turbofish:

tokio::spawn(async move {
    // snip
    Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
});

Note that we need both Send and Sync: the former is necessary for the resulting error to be able to cross the async block boundary, and the latter - because Box<dyn Error + Send> can't be created with From/Into conversion from other errors.


Now, we're left with two more compile errors, independent of the ones before:

error[E0597]: `keys` does not live long enough
  --> src/main.rs:8:14
   |
8  |     for i in keys.iter() {
   |              ^^^^
   |              |
   |              borrowed value does not live long enough
   |              cast requires that `keys` is borrowed for `'static`
...
18 | }
   | - `keys` dropped here while still borrowed

error[E0382]: use of moved value: `client`
  --> src/main.rs:9:33
   |
7  |       let client = reqwest::Client::builder().build()?;
   |           ------ move occurs because `client` has type `reqwest::Client`, which does not implement the `Copy` trait
8  |       for i in keys.iter() {
9  |           tokio::spawn(async move {
   |  _________________________________^
10 | |             let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
11 | |             let res = client.get(url).send().await?.text().await?;
   | |                       ------ use occurs due to use in generator
12 | |             println!("{:?}", res);
13 | |             Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
14 | |         });
   | |_________^ value moved here, in previous iteration of loop

The first of them can be fixed in three possible ways:

  • use Vec insted of array and drop the .iter(),
  • use std::array::IntoIter,
  • use .iter().copied(). In each case, we'll get &'static str, which can be passed into the async block.

The second is even easier: we can simply do let client = client.clone(); at the start of each iteration, before tokio::spawn. This is cheap, since Client uses Arc internally.

Here's the playground, with all the changes described above.


Finally, here's the version which I'd personally recommend to base your code on, since it not only compiles, but additionally does not swallow the errors which can arise on request:

use core::time::Duration;
use tokio::time::sleep;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let keys = ["key", "key", "key"];
    let client = reqwest::Client::builder().build()?;

    for i in std::array::IntoIter::new(keys) {
        let client = client.clone();
        tokio::spawn(async move {
            let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
            // moved actual request into inner block...
            match async move {
                let res = client.get(url).send().await?.text().await?;
                // ...returning Result with explicit error type,
                // so that the wholeinner async block is treated as "try"-block...
                Ok::<_, Box<dyn std::error::Error + Send + Sync>>(res)
            }
            .await
            {
                // ...and matching on the result, to have either text or error
                // (here it'll always be error, due to invalid URL)
                Ok(res) => println!("{:?}", res),
                Err(er) => println!("{}", er),
            }
        });
        sleep(Duration::from_millis(97)).await;
    }

    // just to wait for responses
    sleep(Duration::from_millis(1000)).await;
    Ok(())
}

Playground

Upvotes: 5

Related Questions