@@ -12,10 +12,13 @@ use serde::{Deserialize, Serialize};
1212use tokio:: sync:: { oneshot, Semaphore } ;
1313use wasm_bindgen:: { prelude:: Closure , JsCast , JsValue , UnwrapThrowExt } ;
1414use web_sys:: {
15- Blob , BlobPropertyBag , MessageEvent , MessagePort , Url , Worker , WorkerOptions , WorkerType ,
15+ Blob , BlobPropertyBag , MessageChannel , MessageEvent , MessagePort , Url , Worker , WorkerOptions ,
16+ WorkerType ,
1617} ;
1718
1819use crate :: {
20+ channel:: Channel ,
21+ channel_task:: ChannelTask ,
1922 convert:: { from_bytes, to_bytes} ,
2023 error:: { Full , InitError } ,
2124 func:: { WebWorkerChannelFn , WebWorkerFn } ,
@@ -234,6 +237,10 @@ impl WebWorker {
234237
235238 /// Run an async function with bidirectional channel support on this [`WebWorker`].
236239 ///
240+ /// Returns a [`ChannelTask`] that provides both the communication channel and the
241+ /// task result. The `MessageChannel` is created internally — callers interact only
242+ /// through the returned `ChannelTask`.
243+ ///
237244 /// The `func`: [`WebWorkerChannelFn`] argument should normally be instantiated using the
238245 /// [`crate::webworker_channel!`] macro. This ensures type safety and that the function
239246 /// is correctly exposed to the worker.
@@ -242,19 +249,20 @@ impl WebWorker {
242249 ///
243250 /// Example:
244251 /// ```ignore
245- /// worker.run_channel(webworker_channel!(process_with_progress), &my_data, port).await
252+ /// let task = worker
253+ /// .run_channel(webworker_channel!(process_with_progress), &data)
254+ /// .await;
255+ ///
256+ /// let progress: Progress = task.recv().await.expect("progress");
257+ /// task.send(&Continue { should_continue: true });
258+ /// let result: ProcessResult = task.result().await;
246259 /// ```
247- pub async fn run_channel < T , R > (
248- & self ,
249- func : WebWorkerChannelFn < T , R > ,
250- arg : & T ,
251- port : MessagePort ,
252- ) -> R
260+ pub async fn run_channel < T , R > ( & self , func : WebWorkerChannelFn < T , R > , arg : & T ) -> ChannelTask < R >
253261 where
254262 T : Serialize + for < ' de > Deserialize < ' de > ,
255263 R : Serialize + for < ' de > Deserialize < ' de > ,
256264 {
257- self . run_channel_internal ( func, arg, port ) . await
265+ self . run_channel_internal ( func, arg) . await
258266 }
259267
260268 /// This function differs from [`WebWorker::run`] by returning early if the given task limit is reached.
@@ -364,12 +372,13 @@ impl WebWorker {
364372 }
365373
366374 /// Internal function to schedule a channel task to the worker.
375+ /// Creates a `MessageChannel` internally, sends one port to the worker,
376+ /// and returns a `ChannelTask` wrapping the other port and the result future.
367377 pub ( crate ) async fn run_channel_internal < T , R > (
368378 & self ,
369379 func : WebWorkerChannelFn < T , R > ,
370380 arg : & T ,
371- port : MessagePort ,
372- ) -> R
381+ ) -> ChannelTask < R >
373382 where
374383 T : Serialize + for < ' de > Deserialize < ' de > ,
375384 R : Serialize + for < ' de > Deserialize < ' de > ,
@@ -381,8 +390,15 @@ impl WebWorker {
381390 None
382391 } ;
383392
384- // Convert arg and result.
385- self . force_run ( func. name , arg, true , Some ( port) ) . await
393+ // Create the MessageChannel internally.
394+ let msg_channel = MessageChannel :: new ( ) . expect_throw ( "Could not create MessageChannel" ) ;
395+ let channel = Channel :: from ( msg_channel. port1 ( ) ) ;
396+ let worker_port = msg_channel. port2 ( ) ;
397+
398+ // Send the request and get a receiver for the result bytes.
399+ let result_rx = self . send_channel_request ( func. name , arg, worker_port) ;
400+
401+ ChannelTask :: new ( channel, result_rx)
386402 }
387403
388404 /// This function handles the communication with the worker
@@ -447,6 +463,50 @@ impl WebWorker {
447463 . expect_throw ( "Could not find function" )
448464 }
449465
466+ /// Sends a channel request to the worker and returns a receiver for the result bytes.
467+ /// Unlike `send_request`, this does not await the result — it returns immediately
468+ /// so the caller can interact with the channel before consuming the result.
469+ fn send_channel_request < T > (
470+ & self ,
471+ func_name : & ' static str ,
472+ arg : & T ,
473+ port : MessagePort ,
474+ ) -> oneshot:: Receiver < Vec < u8 > >
475+ where
476+ T : Serialize + for < ' de > Deserialize < ' de > ,
477+ {
478+ let id = self . current_task . fetch_add ( 1 , Ordering :: Relaxed ) ;
479+ let request = Request {
480+ id,
481+ func_name,
482+ is_channel : true ,
483+ arg : to_bytes ( arg) ,
484+ } ;
485+
486+ let ( sender, receiver) = oneshot:: channel ( ) ;
487+ self . open_tasks . borrow_mut ( ) . insert ( id, sender) ;
488+
489+ let transfer = Array :: new ( ) ;
490+ transfer. push ( & port) ;
491+
492+ self . worker
493+ . post_message_with_transfer (
494+ & serde_wasm_bindgen:: to_value ( & request) . expect_throw ( "Could not serialize request" ) ,
495+ & transfer,
496+ )
497+ . expect_throw ( "WebWorker gone" ) ;
498+
499+ // Map the receiver to extract just the response bytes.
500+ let ( byte_sender, byte_receiver) = oneshot:: channel ( ) ;
501+ wasm_bindgen_futures:: spawn_local ( async move {
502+ if let Ok ( response) = receiver. await {
503+ let _ = byte_sender. send ( response. response . expect ( "Could not find function" ) ) ;
504+ }
505+ } ) ;
506+
507+ byte_receiver
508+ }
509+
450510 /// Return the current capacity for new tasks.
451511 pub fn capacity ( & self ) -> Option < usize > {
452512 self . task_limit . as_ref ( ) . map ( |s| s. available_permits ( ) )
0 commit comments