1
1
use std:: cell:: { Cell , RefCell } ;
2
2
use std:: fmt;
3
+ use std:: rc:: Rc ;
3
4
use std:: sync:: atomic:: { AtomicBool , AtomicI32 , Ordering } ;
4
5
use std:: sync:: Arc ;
5
6
@@ -19,9 +20,7 @@ use wasm_bindgen::prelude::*;
19
20
///
20
21
/// Currently this type is constructed with `JsFuture::from`.
21
22
pub struct JsFuture {
22
- resolved : oneshot:: Receiver < JsValue > ,
23
- rejected : oneshot:: Receiver < JsValue > ,
24
- callbacks : Option < ( Closure < dyn FnMut ( JsValue ) > , Closure < dyn FnMut ( JsValue ) > ) > ,
23
+ rx : oneshot:: Receiver < Result < JsValue , JsValue > > ,
25
24
}
26
25
27
26
impl fmt:: Debug for JsFuture {
@@ -33,28 +32,49 @@ impl fmt::Debug for JsFuture {
33
32
impl From < Promise > for JsFuture {
34
33
fn from ( js : Promise ) -> JsFuture {
35
34
// Use the `then` method to schedule two callbacks, one for the
36
- // resolved value and one for the rejected value. These two callbacks
37
- // will be connected to oneshot channels which feed back into our
38
- // future .
35
+ // resolved value and one for the rejected value. We're currently
36
+ // assuming that JS engines will unconditionally invoke precisely one of
37
+ // these callbacks, no matter what .
39
38
//
40
- // This may not be the speediest option today but it should work!
41
- let ( tx1, rx1) = oneshot:: channel ( ) ;
42
- let ( tx2, rx2) = oneshot:: channel ( ) ;
43
- let mut tx1 = Some ( tx1) ;
44
- let resolve = Closure :: wrap ( Box :: new ( move |val| {
45
- drop ( tx1. take ( ) . unwrap ( ) . send ( val) ) ;
46
- } ) as Box < dyn FnMut ( _) > ) ;
47
- let mut tx2 = Some ( tx2) ;
48
- let reject = Closure :: wrap ( Box :: new ( move |val| {
49
- drop ( tx2. take ( ) . unwrap ( ) . send ( val) ) ;
50
- } ) as Box < dyn FnMut ( _) > ) ;
39
+ // Ideally we'd have a way to cancel the callbacks getting invoked and
40
+ // free up state ourselves when this `JsFuture` is dropped. We don't
41
+ // have that, though, and one of the callbacks is likely always going to
42
+ // be invoked.
43
+ //
44
+ // As a result we need to make sure that no matter when the callbacks
45
+ // are invoked they are valid to be called at any time, which means they
46
+ // have to be self-contained. Through the `Closure::once` and some
47
+ // `Rc`-trickery we can arrange for both instances of `Closure`, and the
48
+ // `Rc`, to all be destroyed once the first one is called.
49
+ let ( tx, rx) = oneshot:: channel ( ) ;
50
+ let state = Rc :: new ( RefCell :: new ( None ) ) ;
51
+ let state2 = state. clone ( ) ;
52
+ let resolve = Closure :: once ( move |val| finish ( & state2, Ok ( val) ) ) ;
53
+ let state2 = state. clone ( ) ;
54
+ let reject = Closure :: once ( move |val| finish ( & state2, Err ( val) ) ) ;
51
55
52
56
js. then2 ( & resolve, & reject) ;
53
-
54
- JsFuture {
55
- resolved : rx1,
56
- rejected : rx2,
57
- callbacks : Some ( ( resolve, reject) ) ,
57
+ * state. borrow_mut ( ) = Some ( ( tx, resolve, reject) ) ;
58
+
59
+ return JsFuture { rx } ;
60
+
61
+ fn finish (
62
+ state : & RefCell <
63
+ Option < (
64
+ oneshot:: Sender < Result < JsValue , JsValue > > ,
65
+ Closure < dyn FnMut ( JsValue ) > ,
66
+ Closure < dyn FnMut ( JsValue ) > ,
67
+ ) > ,
68
+ > ,
69
+ val : Result < JsValue , JsValue > ,
70
+ ) {
71
+ match state. borrow_mut ( ) . take ( ) {
72
+ // We don't have any guarantee that anyone's still listening at this
73
+ // point (the Rust `JsFuture` could have been dropped) so simply
74
+ // ignore any errors here.
75
+ Some ( ( tx, _, _) ) => drop ( tx. send ( val) ) ,
76
+ None => wasm_bindgen:: throw_str ( "cannot finish twice" ) ,
77
+ }
58
78
}
59
79
}
60
80
}
@@ -64,19 +84,11 @@ impl Future for JsFuture {
64
84
type Error = JsValue ;
65
85
66
86
fn poll ( & mut self ) -> Poll < JsValue , JsValue > {
67
- // Test if either our resolved or rejected side is finished yet. Note
68
- // that they will return errors if they're disconnected which can't
69
- // happen until we drop the `callbacks` field, which doesn't happen
70
- // till we're done, so we dont need to handle that.
71
- if let Ok ( Async :: Ready ( val) ) = self . resolved . poll ( ) {
72
- drop ( self . callbacks . take ( ) ) ;
73
- return Ok ( val. into ( ) ) ;
87
+ match self . rx . poll ( ) {
88
+ Ok ( Async :: Ready ( val) ) => val. map ( Async :: Ready ) ,
89
+ Ok ( Async :: NotReady ) => Ok ( Async :: NotReady ) ,
90
+ Err ( _) => wasm_bindgen:: throw_str ( "cannot cancel" ) ,
74
91
}
75
- if let Ok ( Async :: Ready ( val) ) = self . rejected . poll ( ) {
76
- drop ( self . callbacks . take ( ) ) ;
77
- return Err ( val) ;
78
- }
79
- Ok ( Async :: NotReady )
80
92
}
81
93
}
82
94
@@ -101,8 +113,8 @@ impl Future for JsFuture {
101
113
/// resolve**. Instead it will be a leaked promise. This is an unfortunate
102
114
/// limitation of wasm currently that's hoped to be fixed one day!
103
115
pub fn future_to_promise < F > ( future : F ) -> Promise
104
- where
105
- F : Future < Item = JsValue , Error = JsValue > + ' static ,
116
+ where
117
+ F : Future < Item = JsValue , Error = JsValue > + ' static ,
106
118
{
107
119
_future_to_promise ( Box :: new ( future) )
108
120
}
@@ -283,9 +295,7 @@ fn _future_to_promise(future: Box<dyn Future<Item = JsValue, Error = JsValue>>)
283
295
break ;
284
296
}
285
297
286
- State :: Waiting ( _) => {
287
- panic ! ( "shouldn't see waiting state!" )
288
- }
298
+ State :: Waiting ( _) => panic ! ( "shouldn't see waiting state!" ) ,
289
299
}
290
300
291
301
let ( val, f) = match me. spawn . borrow_mut ( ) . poll_future_notify ( & me. waker , 0 ) {
@@ -315,8 +325,8 @@ fn _future_to_promise(future: Box<dyn Future<Item = JsValue, Error = JsValue>>)
315
325
///
316
326
/// This function has the same panic behavior as `future_to_promise`.
317
327
pub fn spawn_local < F > ( future : F )
318
- where
319
- F : Future < Item = ( ) , Error = ( ) > + ' static ,
328
+ where
329
+ F : Future < Item = ( ) , Error = ( ) > + ' static ,
320
330
{
321
331
future_to_promise (
322
332
future
0 commit comments