@@ -128,9 +128,7 @@ use wasm_bindgen::prelude::*;
128
128
///
129
129
/// Currently this type is constructed with `JsFuture::from`.
130
130
pub struct JsFuture {
131
- resolved : oneshot:: Receiver < JsValue > ,
132
- rejected : oneshot:: Receiver < JsValue > ,
133
- callbacks : Option < ( Closure < dyn FnMut ( JsValue ) > , Closure < dyn FnMut ( JsValue ) > ) > ,
131
+ rx : oneshot:: Receiver < Result < JsValue , JsValue > > ,
134
132
}
135
133
136
134
impl fmt:: Debug for JsFuture {
@@ -142,28 +140,49 @@ impl fmt::Debug for JsFuture {
142
140
impl From < Promise > for JsFuture {
143
141
fn from ( js : Promise ) -> JsFuture {
144
142
// Use the `then` method to schedule two callbacks, one for the
145
- // resolved value and one for the rejected value. These two callbacks
146
- // will be connected to oneshot channels which feed back into our
147
- // future .
143
+ // resolved value and one for the rejected value. We're currently
144
+ // assuming that JS engines will unconditionally invoke precisely one of
145
+ // these callbacks, no matter what .
148
146
//
149
- // This may not be the speediest option today but it should work!
150
- let ( tx1, rx1) = oneshot:: channel ( ) ;
151
- let ( tx2, rx2) = oneshot:: channel ( ) ;
152
- let mut tx1 = Some ( tx1) ;
153
- let resolve = Closure :: wrap ( Box :: new ( move |val| {
154
- drop ( tx1. take ( ) . unwrap ( ) . send ( val) ) ;
155
- } ) as Box < dyn FnMut ( _) > ) ;
156
- let mut tx2 = Some ( tx2) ;
157
- let reject = Closure :: wrap ( Box :: new ( move |val| {
158
- drop ( tx2. take ( ) . unwrap ( ) . send ( val) ) ;
159
- } ) as Box < dyn FnMut ( _) > ) ;
147
+ // Ideally we'd have a way to cancel the callbacks getting invoked and
148
+ // free up state ourselves when this `JsFuture` is dropped. We don't
149
+ // have that, though, and one of the callbacks is likely always going to
150
+ // be invoked.
151
+ //
152
+ // As a result we need to make sure that no matter when the callbacks
153
+ // are invoked they are valid to be called at any time, which means they
154
+ // have to be self-contained. Through the `Closure::once` and some
155
+ // `Rc`-trickery we can arrange for both instances of `Closure`, and the
156
+ // `Rc`, to all be destroyed once the first one is called.
157
+ let ( tx, rx) = oneshot:: channel ( ) ;
158
+ let state = Rc :: new ( RefCell :: new ( None ) ) ;
159
+ let state2 = state. clone ( ) ;
160
+ let resolve = Closure :: once ( move |val| finish ( & state2, Ok ( val) ) ) ;
161
+ let state2 = state. clone ( ) ;
162
+ let reject = Closure :: once ( move |val| finish ( & state2, Err ( val) ) ) ;
160
163
161
164
js. then2 ( & resolve, & reject) ;
165
+ * state. borrow_mut ( ) = Some ( ( tx, resolve, reject) ) ;
166
+
167
+ return JsFuture { rx } ;
162
168
163
- JsFuture {
164
- resolved : rx1,
165
- rejected : rx2,
166
- callbacks : Some ( ( resolve, reject) ) ,
169
+ fn finish (
170
+ state : & RefCell <
171
+ Option < (
172
+ oneshot:: Sender < Result < JsValue , JsValue > > ,
173
+ Closure < dyn FnMut ( JsValue ) > ,
174
+ Closure < dyn FnMut ( JsValue ) > ,
175
+ ) > ,
176
+ > ,
177
+ val : Result < JsValue , JsValue > ,
178
+ ) {
179
+ match state. borrow_mut ( ) . take ( ) {
180
+ // We don't have any guarantee that anyone's still listening at this
181
+ // point (the Rust `JsFuture` could have been dropped) so simply
182
+ // ignore any errors here.
183
+ Some ( ( tx, _, _) ) => drop ( tx. send ( val) ) ,
184
+ None => wasm_bindgen:: throw_str ( "cannot finish twice" ) ,
185
+ }
167
186
}
168
187
}
169
188
}
@@ -173,19 +192,11 @@ impl Future for JsFuture {
173
192
type Error = JsValue ;
174
193
175
194
fn poll ( & mut self ) -> Poll < JsValue , JsValue > {
176
- // Test if either our resolved or rejected side is finished yet. Note
177
- // that they will return errors if they're disconnected which can't
178
- // happen until we drop the `callbacks` field, which doesn't happen
179
- // till we're done, so we dont need to handle that.
180
- if let Ok ( Async :: Ready ( val) ) = self . resolved . poll ( ) {
181
- drop ( self . callbacks . take ( ) ) ;
182
- return Ok ( val. into ( ) ) ;
183
- }
184
- if let Ok ( Async :: Ready ( val) ) = self . rejected . poll ( ) {
185
- drop ( self . callbacks . take ( ) ) ;
186
- return Err ( val) ;
195
+ match self . rx . poll ( ) {
196
+ Ok ( Async :: Ready ( val) ) => val. map ( Async :: Ready ) ,
197
+ Ok ( Async :: NotReady ) => Ok ( Async :: NotReady ) ,
198
+ Err ( _) => wasm_bindgen:: throw_str ( "cannot cancel" ) ,
187
199
}
188
- Ok ( Async :: NotReady )
189
200
}
190
201
}
191
202
0 commit comments