From 532243e8fad3e6bede3c8107435a2791c08ba3d1 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:58:33 -0400 Subject: [PATCH 1/8] feat: add way to block on async to from sync code Wraps function for futures crate to block on an async task --- Cargo.toml | 2 +- src/lib.rs | 2 ++ src/wrappers.rs | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fe930d9..3698a23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ description = "Wrapper around reqwest for use in both native and wasm" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +futures = "0.3.28" reqwest = { version = "0.12.3", default-features = false } # For native compilation @@ -27,7 +28,6 @@ js-sys = { version = "0.3.69", optional = true } web-sys = { version = "0.3.69", optional = true } [dev-dependencies] -futures = "0.3.28" reqwest = { version = "0.12.3" } wasm-bindgen-test = "0.3.34" diff --git a/src/lib.rs b/src/lib.rs index 876acd6..e9952de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,4 +75,6 @@ pub use wrappers::fetch; #[cfg(feature = "yield_now")] pub use yield_::yield_now; +pub use wrappers::block_on; + pub use reqwest::Client; // Exported to make it easier to use without a second import and maintain semver diff --git a/src/wrappers.rs b/src/wrappers.rs index 9f8ee30..a603d86 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -43,4 +43,14 @@ where crate::wasm::fetch(request, on_done); } +/// Provides a cross platform compatible way to run an async function in a blocking fashion. +/// Intended for use in callbacks as this will block but call backs are not async so we need sync code +pub fn block_on(future: F) -> F::Output +where + F: std::future::Future + Send + 'static, + F::Output: Send + 'static, +{ + futures::executor::block_on(future) +} + // TODO 3: Test link in documentation after pushing to main From 165933904b3ef767192d66dab042abc33e92bfc6 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 16 Jul 2024 00:49:06 -0400 Subject: [PATCH 2/8] feat: relax constraints to match target Match constraints of the method being called. Was not working in WASM with constraints and they are needed. --- src/wrappers.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/wrappers.rs b/src/wrappers.rs index a603d86..80f7d0f 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,6 +1,8 @@ //! Stores the wrapper functions that can be called from either native or wasm //! code +use futures::Future; + /// Performs a HTTP requests and calls the given callback when done. NB: Needs /// to use a callback to prevent blocking on the thread that initiates the /// fetch. Note: Instead of calling get like in the example you can use post, @@ -45,11 +47,7 @@ where /// Provides a cross platform compatible way to run an async function in a blocking fashion. /// Intended for use in callbacks as this will block but call backs are not async so we need sync code -pub fn block_on(future: F) -> F::Output -where - F: std::future::Future + Send + 'static, - F::Output: Send + 'static, -{ +pub fn block_on(future: F) -> F::Output { futures::executor::block_on(future) } From 3b634566a828b81e1fd484f55e5591181b739e49 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:24:52 -0400 Subject: [PATCH 3/8] refactor: split off spawn from the rest for reuse --- src/native.rs | 10 ++++------ src/wasm.rs | 9 +++------ src/wrappers.rs | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/native.rs b/src/native.rs index b62a1f1..0373fe8 100644 --- a/src/native.rs +++ b/src/native.rs @@ -4,12 +4,10 @@ compile_error!("Must chose a native runtime by enabling a feature flag. Right now only tokio is supported. If you have a different runtime that you want please create an issue on github."); #[cfg(feature = "native-tokio")] -pub fn fetch(request: reqwest::RequestBuilder, on_done: F) +pub fn spawn(future: F) where - F: 'static + Send + FnOnce(Result), + F: 'static + Send + futures::Future, + F::Output: Send + 'static, { - tokio::spawn(async move { - let result = request.send().await; - on_done(result) - }); + tokio::spawn(future); } diff --git a/src/wasm.rs b/src/wasm.rs index 5be290c..a77aefe 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,11 +1,8 @@ //! Stores the code specific to wasm compilations -pub fn fetch(request: reqwest::RequestBuilder, on_done: F) +pub fn spawn(future: F) where - F: 'static + Send + FnOnce(reqwest::Result), + F: futures::Future + 'static, { - wasm_bindgen_futures::spawn_local(async move { - let result = request.send().await; - on_done(result) - }); + wasm_bindgen_futures::spawn_local(future); } diff --git a/src/wrappers.rs b/src/wrappers.rs index 80f7d0f..c4fcc9a 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -37,12 +37,24 @@ use futures::Future; pub fn fetch(request: reqwest::RequestBuilder, on_done: F) where F: 'static + Send + FnOnce(reqwest::Result), +{ + let future = async move { + let result = request.send().await; + on_done(result) + }; + spawn(future); +} + +/// Spawns a future on the underlying runtime in a cross platform way +pub fn spawn(future: F) +where + F: futures::Future + 'static + Send, { #[cfg(not(target_arch = "wasm32"))] - crate::native::fetch(request, on_done); + crate::native::spawn(future); #[cfg(target_arch = "wasm32")] - crate::wasm::fetch(request, on_done); + crate::wasm::spawn(future); } /// Provides a cross platform compatible way to run an async function in a blocking fashion. From 538d0c11601e7b1362ae959b937c31614eb41744 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:43:33 -0400 Subject: [PATCH 4/8] fix: replace blocking impl with a busy loop --- src/lib.rs | 2 +- src/wrappers.rs | 33 +++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e9952de..cc96cc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,6 @@ pub use wrappers::fetch; #[cfg(feature = "yield_now")] pub use yield_::yield_now; -pub use wrappers::block_on; +pub use wrappers::wait_for; pub use reqwest::Client; // Exported to make it easier to use without a second import and maintain semver diff --git a/src/wrappers.rs b/src/wrappers.rs index c4fcc9a..1f007f2 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,6 +1,8 @@ //! Stores the wrapper functions that can be called from either native or wasm //! code +use std::fmt::Debug; + use futures::Future; /// Performs a HTTP requests and calls the given callback when done. NB: Needs @@ -57,10 +59,33 @@ where crate::wasm::spawn(future); } -/// Provides a cross platform compatible way to run an async function in a blocking fashion. -/// Intended for use in callbacks as this will block but call backs are not async so we need sync code -pub fn block_on(future: F) -> F::Output { - futures::executor::block_on(future) +/// Sometimes needed in callbacks to call async code from the sync context +/// Provides a cross platform compatible way to run an async function in a +/// blocking fashion, but without actually blocking because then the function +/// cannot complete and results in a deadlock. +/// +/// # Warning +/// Until a better solution can be found this will result in a busy loop so use +/// sparingly. +pub fn wait_for(future: F) -> F::Output +where + F: Future + Send + 'static, + F::Output: Debug + Send, +{ + let (tx, mut rx) = futures::channel::oneshot::channel(); + spawn(async move { + tx.send(future.await) + .expect("failed to send but expected rx to still be available"); + }); + loop { + // TODO 4: Is there a better way to do this than a busy loop? + if let Some(x) = rx + .try_recv() + .expect("failed to receive. maybe sender panicked") + { + return x; + } + } } // TODO 3: Test link in documentation after pushing to main From 502e490852e9514591116c8cc6a3803f7830e911 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:54:51 -0400 Subject: [PATCH 5/8] feat: split the requirements for spawn & wait_for WASM doesn't require Send and we need to use it for types that are not Send in WASM. --- src/lib.rs | 1 + src/wrappers.rs | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cc96cc9..f82c979 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![forbid(unsafe_code)] #![cfg_attr(test, deny(warnings))] +// dox - used as documentation for duplicate wasm functions (Uncertain if this will cause problems but seen this in Reqwest) //! # reqwest-cross //! diff --git a/src/wrappers.rs b/src/wrappers.rs index 1f007f2..0192cc0 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,8 +1,6 @@ //! Stores the wrapper functions that can be called from either native or wasm //! code -use std::fmt::Debug; - use futures::Future; /// Performs a HTTP requests and calls the given callback when done. NB: Needs @@ -47,15 +45,21 @@ where spawn(future); } +#[cfg(not(target_arch = "wasm32"))] /// Spawns a future on the underlying runtime in a cross platform way pub fn spawn(future: F) where F: futures::Future + 'static + Send, { - #[cfg(not(target_arch = "wasm32"))] crate::native::spawn(future); +} - #[cfg(target_arch = "wasm32")] +#[cfg(target_arch = "wasm32")] +/// dox +pub fn spawn(future: F) +where + F: futures::Future + 'static, +{ crate::wasm::spawn(future); } @@ -67,16 +71,35 @@ where /// # Warning /// Until a better solution can be found this will result in a busy loop so use /// sparingly. +#[cfg(not(target_arch = "wasm32"))] pub fn wait_for(future: F) -> F::Output where - F: Future + Send + 'static, - F::Output: Debug + Send, + F: Future + 'static + Send, + F::Output: Send, { - let (tx, mut rx) = futures::channel::oneshot::channel(); + let (tx, rx) = futures::channel::oneshot::channel(); spawn(async move { tx.send(future.await) - .expect("failed to send but expected rx to still be available"); + .unwrap_or_else(|_| eprintln!("failed to send but expected rx to still be available")); }); + wait_for_receiver(rx) +} + +#[cfg(target_arch = "wasm32")] +/// dox +pub fn wait_for(future: F) -> F::Output +where + F: Future + 'static, +{ + let (tx, rx) = futures::channel::oneshot::channel(); + spawn(async move { + tx.send(future.await) + .unwrap_or_else(|_| eprintln!("failed to send but expected rx to still be available")); + }); + wait_for_receiver(rx) +} + +fn wait_for_receiver(mut rx: futures::channel::oneshot::Receiver) -> T { loop { // TODO 4: Is there a better way to do this than a busy loop? if let Some(x) = rx From 3517fa18fd0b4328847114df321c3c9e6e71c826 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:34:53 -0400 Subject: [PATCH 6/8] fix: rip out wait_for because it still blocks --- src/lib.rs | 2 -- src/wrappers.rs | 50 ------------------------------------------------- 2 files changed, 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f82c979..4d30d77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,4 @@ pub use wrappers::fetch; #[cfg(feature = "yield_now")] pub use yield_::yield_now; -pub use wrappers::wait_for; - pub use reqwest::Client; // Exported to make it easier to use without a second import and maintain semver diff --git a/src/wrappers.rs b/src/wrappers.rs index 0192cc0..c128f05 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,8 +1,6 @@ //! Stores the wrapper functions that can be called from either native or wasm //! code -use futures::Future; - /// Performs a HTTP requests and calls the given callback when done. NB: Needs /// to use a callback to prevent blocking on the thread that initiates the /// fetch. Note: Instead of calling get like in the example you can use post, @@ -63,52 +61,4 @@ where crate::wasm::spawn(future); } -/// Sometimes needed in callbacks to call async code from the sync context -/// Provides a cross platform compatible way to run an async function in a -/// blocking fashion, but without actually blocking because then the function -/// cannot complete and results in a deadlock. -/// -/// # Warning -/// Until a better solution can be found this will result in a busy loop so use -/// sparingly. -#[cfg(not(target_arch = "wasm32"))] -pub fn wait_for(future: F) -> F::Output -where - F: Future + 'static + Send, - F::Output: Send, -{ - let (tx, rx) = futures::channel::oneshot::channel(); - spawn(async move { - tx.send(future.await) - .unwrap_or_else(|_| eprintln!("failed to send but expected rx to still be available")); - }); - wait_for_receiver(rx) -} - -#[cfg(target_arch = "wasm32")] -/// dox -pub fn wait_for(future: F) -> F::Output -where - F: Future + 'static, -{ - let (tx, rx) = futures::channel::oneshot::channel(); - spawn(async move { - tx.send(future.await) - .unwrap_or_else(|_| eprintln!("failed to send but expected rx to still be available")); - }); - wait_for_receiver(rx) -} - -fn wait_for_receiver(mut rx: futures::channel::oneshot::Receiver) -> T { - loop { - // TODO 4: Is there a better way to do this than a busy loop? - if let Some(x) = rx - .try_recv() - .expect("failed to receive. maybe sender panicked") - { - return x; - } - } -} - // TODO 3: Test link in documentation after pushing to main From 2061141b4f5170eadf9df58532e9fa87ccecba77 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:14:17 -0400 Subject: [PATCH 7/8] feat!: make callback async BREAKING CHANGE: type of callback changed --- examples/loop_yield.rs | 2 +- examples/simple_fetch.rs | 2 +- src/wrappers.rs | 24 ++++++++++++++++++++---- tests/web.rs | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/loop_yield.rs b/examples/loop_yield.rs index 472a916..783ec19 100644 --- a/examples/loop_yield.rs +++ b/examples/loop_yield.rs @@ -42,7 +42,7 @@ async fn common_code() -> Result<(), Box> { let (tx, rx) = futures::channel::oneshot::channel(); fetch( request, - move |result: Result| { + move |result: Result| async { tx.send(result.expect("Expecting Response not Error").status()) .expect("Receiver should still be available"); }, diff --git a/examples/simple_fetch.rs b/examples/simple_fetch.rs index b443ede..1dedd86 100644 --- a/examples/simple_fetch.rs +++ b/examples/simple_fetch.rs @@ -26,7 +26,7 @@ async fn common_code() -> Result<(), Box> { fetch( request, - move |result: Result| { + move |result: Result| async { tx.send(result.expect("Expecting Response not Error").status()) .expect("Receiver should still be available"); }, diff --git a/src/wrappers.rs b/src/wrappers.rs index c128f05..101eff7 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -1,6 +1,7 @@ //! Stores the wrapper functions that can be called from either native or wasm //! code +#[cfg(not(target_arch = "wasm32"))] /// Performs a HTTP requests and calls the given callback when done. NB: Needs /// to use a callback to prevent blocking on the thread that initiates the /// fetch. Note: Instead of calling get like in the example you can use post, @@ -19,7 +20,7 @@ /// let request = client.get("http://httpbin.org/get"); /// let (tx, rx) = futures::channel::oneshot::channel(); /// -/// fetch(request, move |result: Result| { +/// fetch(request, move |result: Result| async { /// tx.send(result.expect("Expecting Response not Error").status()) /// .expect("Receiver should still be available"); /// }); @@ -32,13 +33,28 @@ /// # #[cfg(target_arch = "wasm32")] /// # fn main(){} /// ``` -pub fn fetch(request: reqwest::RequestBuilder, on_done: F) +pub fn fetch(request: reqwest::RequestBuilder, on_done: F) +where + F: 'static + Send + FnOnce(reqwest::Result) -> O, + O: futures::Future + Send, +{ + let future = async move { + let result = request.send().await; + on_done(result).await; + }; + spawn(future); +} + +/// dox +#[cfg(target_arch = "wasm32")] +pub fn fetch(request: reqwest::RequestBuilder, on_done: F) where - F: 'static + Send + FnOnce(reqwest::Result), + F: 'static + FnOnce(reqwest::Result) -> O, + O: futures::Future, { let future = async move { let result = request.send().await; - on_done(result) + on_done(result).await; }; spawn(future); } diff --git a/tests/web.rs b/tests/web.rs index 4df2377..a12a0f6 100644 --- a/tests/web.rs +++ b/tests/web.rs @@ -12,7 +12,7 @@ fn main() { fetch( request, - move |result: Result| { + move |result: Result| async { tx.send(result.expect("Expecting Response not Error").status()) .expect("Receiver should still be available"); }, From 7d5f1f589221249b785792cdc3a462a9dd6c00a7 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:59:24 -0400 Subject: [PATCH 8/8] chore: version bump --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3624fcf..9d4d5f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,7 +678,7 @@ dependencies = [ [[package]] name = "reqwest-cross" -version = "0.3.1" +version = "0.4.0" dependencies = [ "futures", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 3698a23..a2f4c4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reqwest-cross" -version = "0.3.1" +version = "0.4.0" authors = ["One "] categories = ["web-programming::http-client", "wasm"] documentation = "https://docs.rs/reqwest-cross"