Back to Blog

Async Commands in Tauri: Background Tasks with Progress Events | Rust + React

Sandy LaneSandy Lane

Video: Async Commands in Tauri: Background Tasks with Progress Events | Rust + React by Taught by Celeste AI - AI Coding Coach

Watch full page →

Async Commands in Tauri: Background Tasks with Progress Events

Running long operations directly in Tauri can freeze your UI, but async commands let you keep your app responsive by running tasks in the background. This example shows how to create an async Rust command that emits progress events to a React frontend, enabling a live progress bar that updates smoothly during the operation.

Code

use serde::Serialize;
use std::time::Duration;
use tauri::{AppHandle, Manager};
use tokio::time::sleep;

#[derive(Clone, Serialize)]
struct ProgressPayload {
  progress: u32,
  message: String,
  is_complete: bool,
}

// Async Tauri command that simulates a long-running task with progress updates
#[tauri::command]
async fn process_task(app: AppHandle, total_steps: u32) -> Result {
  for step in 1..=total_steps {
    // Simulate work with a non-blocking 100ms delay
    sleep(Duration::from_millis(100)).await;

    // Calculate progress percentage
    let progress = (step * 100) / total_steps;

    // Create progress payload
    let payload = ProgressPayload {
      progress,
      message: format!("Step {} of {}", step, total_steps),
      is_complete: false,
    };

    // Emit progress event to frontend
    app.emit_all("progress-event", payload).map_err(|e| e.to_string())?;
  }

  // Emit final complete event
  let complete_payload = ProgressPayload {
    progress: 100,
    message: "Task complete!".into(),
    is_complete: true,
  };
  app.emit_all("progress-event", complete_payload).map_err(|e| e.to_string())?;

  Ok("Processing finished successfully".into())
}

// In your main.rs or lib.rs, register the command:
// tauri::Builder::default()
//   .invoke_handler(tauri::generate_handler![process_task])
//   .run(tauri::generate_context!())
//   .expect("error while running tauri application");

Key Points

  • Marking a Tauri command as async fn runs it on Tokio's thread pool, preventing UI blocking.
  • Use tokio::time::sleep().await to simulate or perform non-blocking delays inside async tasks.
  • Emit progress updates from Rust to the frontend using app.emit_all("event-name", payload) with serializable data.
  • Frontend can listen to these events to update UI elements like progress bars in real time.
  • Always register your async commands with invoke_handler so they are callable from the frontend.