perf: parallelize independent async calls in detail pages

Use tokio::try_join! to run independent repo queries and image URL
lookups concurrently in bag, brew, and checkin page handlers, and for
gear option loading in the brew form data loader.
This commit is contained in:
Jon Seager 2026-02-13 13:06:10 +00:00
parent b8a26bbeca
commit 19e191e922
No known key found for this signature in database
3 changed files with 41 additions and 22 deletions

View file

@ -27,11 +27,20 @@ pub(crate) async fn bag_detail_page(
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))?;
let roast = state let (roast, image_url) = tokio::try_join!(
async {
state
.roast_repo .roast_repo
.get(bag.bag.roast_id) .get(bag.bag.roast_id)
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))
},
async {
Ok::<_, StatusCode>(
resolve_image_url(&state, "roast", i64::from(bag.bag.roast_id)).await,
)
},
)?;
let roaster = state let roaster = state
.roaster_repo .roaster_repo
@ -39,8 +48,6 @@ pub(crate) async fn bag_detail_page(
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))?;
let image_url = resolve_image_url(&state, "roast", i64::from(roast.id)).await;
let view = BagDetailView::from_parts(bag, &roast, &roaster); let view = BagDetailView::from_parts(bag, &roast, &roaster);
let template = BagDetailTemplate { let template = BagDetailTemplate {

View file

@ -27,17 +27,29 @@ pub(crate) async fn brew_detail_page(
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))?;
let bag = state let (bag, brew_image_url) = tokio::try_join!(
async {
state
.bag_repo .bag_repo
.get(brew_details.brew.bag_id) .get(brew_details.brew.bag_id)
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))
},
async { Ok::<_, StatusCode>(resolve_image_url(&state, "brew", i64::from(id)).await) },
)?;
let roast = state let (roast, roast_image_url) = tokio::try_join!(
async {
state
.roast_repo .roast_repo
.get(bag.roast_id) .get(bag.roast_id)
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))
},
async {
Ok::<_, StatusCode>(resolve_image_url(&state, "roast", i64::from(bag.roast_id)).await)
},
)?;
let roaster = state let roaster = state
.roaster_repo .roaster_repo
@ -45,9 +57,7 @@ pub(crate) async fn brew_detail_page(
.await .await
.map_err(|e| map_app_error(e.into()))?; .map_err(|e| map_app_error(e.into()))?;
let image_url = resolve_image_url(&state, "brew", i64::from(id)) let image_url = brew_image_url.or(roast_image_url);
.await
.or(resolve_image_url(&state, "roast", i64::from(roast.id)).await);
let view = BrewDetailView::from_parts(brew_details, &roast, &roaster); let view = BrewDetailView::from_parts(brew_details, &roast, &roaster);

View file

@ -18,8 +18,10 @@ pub(crate) async fn checkin_page(
return Ok(Redirect::to("/login").into_response()); return Ok(Redirect::to("/login").into_response());
} }
let roast_options = load_roast_options(&state).await.map_err(map_app_error)?; let (roast_options, cafe_options) = tokio::try_join!(
let cafe_options = load_cafe_options(&state).await.map_err(map_app_error)?; async { load_roast_options(&state).await.map_err(map_app_error) },
async { load_cafe_options(&state).await.map_err(map_app_error) },
)?;
let template = CheckInTemplate { let template = CheckInTemplate {
nav_active: "checkin", nav_active: "checkin",