2

我正在使用 reqwest,并尝试每 97 毫秒发送一次请求。但是,我不想等待最后一个请求发生或被读取。

我只想请求每 97 毫秒发送一次,并始终将输出发送到标准输出。

我的(当前)代码是这样的:(keys是一个带有 api 键的数组)

    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);
        }
}

如果我删除 awaits,编译器会告诉我error[E0599]: no method named `text` found for opaque type `impl std::future::Future` in the current scope.

TL;DR:我想每 97 毫秒发送一次获取请求,而不管任何响应。如果/当有一个响应管道响应标准输出。

编辑:

我尝试使用线程,但我真的不知道如何使用。这是我想出的:

#[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;
}
}

尝试运行时出现此错误:

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>`
4

1 回答 1

2

您已经发现这tokio::spawn是完成此任务的正确工具,因为您本质上想以“即发即弃”的方式使用一些异步代码,而无需在主程序流程中等待它。您只需要对代码进行一些调整。

首先,对于您引用的错误 - 这是因为您必须以某种方式处理请求期间可能出现的错误。await您可以在每次返回后简单地添加问号Result,但随后会遇到以下情况:

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`

“这个函数”位可能有点误导,因为错误的其余部分是在谈论“异步块”,但要点很简单:如果你使用问号从某个地方冒泡错误,快乐的路径必须返回Result,也。好吧,让我们简单地添加Ok(())到 async 块的末尾,这是下一个错误:

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>`

这也是意料之中的——在普通函数中,返回类型将由其签名提供,但在异步块中,这不是一个选项。但是,我们可以使用turbofish:

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

请注意,我们需要Sendand Sync:前者是产生的错误能够跨越异步块边界所必需的,而后者 - 因为Box<dyn Error + Send> 不能通过From/Into其他错误的转换来创建。


现在,我们还有两个编译错误,与之前的错误无关:

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

其中第一个可以通过三种可能的方式修复:

  • 使用Vecinsted of array 并删除.iter(),
  • 使用std::array::IntoIter,
  • 使用.iter().copied(). 在每种情况下,我们都会得到&'static str,它可以传递到异步块中。

第二个更容易:我们可以简单地let client = client.clone();在每次迭代的开始,before tokio::spawn。这很便宜,因为在内部Client使用Arc.

这是游乐场,上面描述了所有的变化。


最后,这是我个人推荐的代码版本,因为它不仅可以编译,而且不会吞下根据请求可能出现的错误:

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(())
}

操场

于 2021-06-12T08:09:26.927 回答