From 0a46e017b86216119cb2d8f86254132f898c9e9a Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Wed, 22 Feb 2023 13:12:29 -0600 Subject: [PATCH] Make wasm requests cancel when the future drops. --- Cargo.toml | 2 ++ src/wasm/client.rs | 7 +++++-- src/wasm/mod.rs | 26 ++++++++++++++++++++++++++ src/wasm/response.rs | 10 +++++++++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1102d4e87..6dd3f1639 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,6 +159,8 @@ wasm-streams = { version = "0.2", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] version = "0.3.25" features = [ + "AbortController", + "AbortSignal", "Headers", "Request", "RequestInit", diff --git a/src/wasm/client.rs b/src/wasm/client.rs index 56849db77..cfe97bf03 100644 --- a/src/wasm/client.rs +++ b/src/wasm/client.rs @@ -4,7 +4,7 @@ use std::{fmt, future::Future, sync::Arc}; use url::Url; use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _}; -use super::{Request, RequestBuilder, Response}; +use super::{AbortGuard, Request, RequestBuilder, Response}; use crate::IntoUrl; #[wasm_bindgen] @@ -216,6 +216,9 @@ async fn fetch(req: Request) -> crate::Result { } } + let abort = AbortGuard::new()?; + init.signal(Some(&abort.signal())); + let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init) .map_err(crate::error::wasm) .map_err(crate::error::builder)?; @@ -247,7 +250,7 @@ async fn fetch(req: Request) -> crate::Result { } resp.body(js_resp) - .map(|resp| Response::new(resp, url)) + .map(|resp| Response::new(resp, url, abort)) .map_err(crate::error::request) } diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index 249b4a284..e99fb11fb 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -1,4 +1,5 @@ use wasm_bindgen::JsCast; +use web_sys::{AbortController, AbortSignal}; mod body; mod client; @@ -25,3 +26,28 @@ where .dyn_into::() .map_err(|_js_val| "promise resolved to unexpected type".into()) } + +/// A guard that cancels a fetch request when dropped. +struct AbortGuard { + ctrl: AbortController, +} + +impl AbortGuard { + fn new() -> crate::Result { + Ok(AbortGuard { + ctrl: AbortController::new() + .map_err(crate::error::wasm) + .map_err(crate::error::builder)?, + }) + } + + fn signal(&self) -> AbortSignal { + self.ctrl.signal() + } +} + +impl Drop for AbortGuard { + fn drop(&mut self) { + self.ctrl.abort(); + } +} diff --git a/src/wasm/response.rs b/src/wasm/response.rs index 83c7a98c1..9b98da4f3 100644 --- a/src/wasm/response.rs +++ b/src/wasm/response.rs @@ -5,6 +5,8 @@ use http::{HeaderMap, StatusCode}; use js_sys::Uint8Array; use url::Url; +use crate::wasm::AbortGuard; + #[cfg(feature = "stream")] use wasm_bindgen::JsCast; @@ -17,16 +19,22 @@ use serde::de::DeserializeOwned; /// A Response to a submitted `Request`. pub struct Response { http: http::Response, + _abort: AbortGuard, // Boxed to save space (11 words to 1 word), and it's not accessed // frequently internally. url: Box, } impl Response { - pub(super) fn new(res: http::Response, url: Url) -> Response { + pub(super) fn new( + res: http::Response, + url: Url, + abort: AbortGuard, + ) -> Response { Response { http: res, url: Box::new(url), + _abort: abort, } }