1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
use std; use std::fmt; use std::marker::PhantomData; use discard::Discard; use webcore::once::Once; use webcore::value::{Value, Reference}; use webcore::try_from::{TryInto, TryFrom}; use webcore::discard::DiscardOnDrop; #[cfg(feature = "futures-support")] use webcore::serialization::JsSerialize; #[cfg(feature = "futures-support")] use futures_core::TryFuture; #[cfg(feature = "futures-support")] use futures_util::{FutureExt, TryFutureExt}; #[cfg(feature = "futures-support")] use futures_util::future::ready; #[cfg(feature = "futures-support")] use futures_channel::oneshot::channel; #[cfg(feature = "futures-support")] use super::promise_future::{PromiseFuture, spawn_local}; /// This is used to cleanup the [`done`](struct.Promise.html#method.done) callback. /// /// See the documentation for [`done`](struct.Promise.html#method.done) for more information. #[derive( Debug, Clone )] pub struct DoneHandle { state: Value, } impl Discard for DoneHandle { fn discard( self ) { js! { @(no_return) var state = @{&self.state}; state.cancelled = true; state.callback.drop(); } } } /// A `Promise` object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. /// /// In most situations you shouldn't use this, use [`PromiseFuture`](struct.PromiseFuture.html) instead. /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] #[reference(instance_of = "Promise")] pub struct Promise( Reference ); impl Promise { // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions fn is_thenable( input: &Reference ) -> bool { (js! { var input = @{input}; // This emulates the `Type(input) is Object` and `IsCallable(input.then)` ECMAScript abstract operations. return Object( input ) === input && typeof input.then === "function"; }).try_into().unwrap() } /// This function should rarely be needed, use [`PromiseFuture`](struct.PromiseFuture.html) instead. /// /// This function is needed if you have a JavaScript value which is a Promise-like object /// (it has a `then` method) but it isn't a true `Promise`. /// /// That situation is rare, but it can happen if you are using a Promise library such as jQuery or /// Bluebird. /// /// In that situation you can use `Promise::from_thenable` to convert it into a true `Promise`. /// /// If the `input` isn't a Promise-like object then it returns `None`. /// /// # Examples /// /// Convert a Promise-like object to a `Promise`: /// /// ```rust /// // jQuery Promise /// Promise::from_thenable(&js!( return $.get("test.php"); ).try_into().unwrap()) /// /// // Bluebird Promise /// Promise::from_thenable(&js!( return bluebird_promise.timeout(1000); ).try_into().unwrap()) /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) // https://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions // https://www.ecma-international.org/ecma-262/6.0/#sec-promiseresolvethenablejob pub fn from_thenable( input: &Reference ) -> Option< Self > { // TODO this can probably be made more efficient if Promise::is_thenable( input ) { Some( js!( return Promise.resolve( @{input} ); ).try_into().unwrap() ) } else { None } } /// This function converts a Rust Future into a JavaScript Promise. /// /// This is needed when you want to pass a Rust Future into JavaScript. /// /// If you simply want to use a JavaScript Promise inside Rust, then you /// don't need to use this function: you should use /// [`PromiseFuture`](struct.PromiseFuture.html) instead. /// /// # Examples /// /// Convert a Rust Future into a JavaScript Promise: /// /// ```rust /// Promise::from_future(rust_future) /// ``` /// /// Export a Rust Future so that it can be used in JavaScript /// (this only works with the `wasm32-unknown-unknown` target): /// /// ```rust /// #[js_export] /// fn foo() -> Promise { /// Promise::from_future(rust_future) /// } /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#Syntax) // https://www.ecma-international.org/ecma-262/6.0/#sec-promise-executor #[cfg(feature = "futures-support")] pub fn from_future< A >( future: A ) -> Self where A: TryFuture + 'static, A::Ok: JsSerialize, A::Error: JsSerialize { let future = future.into_future(); #[inline] fn call< A: JsSerialize >( f: Reference, value: A ) { js! { @(no_return) @{f}( @{value} ); } } let callback = move |success: Reference, error: Reference| { spawn_local( future.then( move |result| { match result { Ok( a ) => call( success, a ), Err( a ) => call( error, a ), } ready( () ) } ) ); }; js!( return new Promise( @{Once( callback )} ); ).try_into().unwrap() } /// This method is usually not needed, use [`PromiseFuture`](struct.PromiseFuture.html) instead. /// /// When the `Promise` either succeeds or fails, it calls the `callback` with the result. /// /// It does not wait for the `Promise` to succeed / fail (it does not block the thread). /// /// The `callback` is guaranteed to be called asynchronously even if the `Promise` is already succeeded / failed. /// /// If the `Promise` never succeeds / fails then the `callback` will never be called. /// /// This method returns a [`DoneHandle`](struct.DoneHandle.html). The [`DoneHandle`](struct.DoneHandle.html) /// *exclusively* owns the `callback`, so when the [`DoneHandle`](struct.DoneHandle.html) is dropped it will /// drop the `callback` and the `callback` will never be called. This will happen even if the `Promise` is not dropped! /// /// Dropping the [`DoneHandle`](struct.DoneHandle.html) does ***not*** cancel the `Promise`, because promises /// do not support cancellation. /// /// If you are no longer interested in the `Promise`'s result you can simply drop the [`DoneHandle`](struct.DoneHandle.html) /// and then the `callback` will never be called. /// /// But if you *are* interested in the `Promise`'s result, then you have two choices: /// /// * Keep the [`DoneHandle`](struct.DoneHandle.html) alive until after the `callback` is called (by storing it in a /// variable or data structure). /// /// * Use the [`leak`](struct.DiscardOnDrop.html#method.leak) method to leak the [`DoneHandle`](struct.DoneHandle.html). /// If the `Promise` never succeeds or fails then this ***will*** leak the memory of the callback, so only use /// [`leak`](struct.DiscardOnDrop.html#method.leak) if you need to. /// /// # Examples /// /// Normal usage: /// /// ```rust /// let handle = promise.done(|result| { /// match result { /// Ok(success) => { ... }, /// Err(error) => { ... }, /// } /// }); /// ``` /// /// Leak the [`DoneHandle`](struct.DoneHandle.html) and `callback`: /// /// ```rust /// promise.done(|result| { /// match result { /// Ok(success) => { ... }, /// Err(error) => { ... }, /// } /// }).leak(); /// ``` /// /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) // https://www.ecma-international.org/ecma-262/6.0/#sec-performpromisethen pub fn done< A, B, F >( &self, callback: F ) -> DiscardOnDrop< DoneHandle > where A: TryFrom< Value >, B: TryFrom< Value >, // TODO these Debug constraints are only needed because of unwrap A::Error: std::fmt::Debug, B::Error: std::fmt::Debug, F: FnOnce( Result< A, B > ) + 'static { let callback = move |value: Value, success: bool| { let value: Result< A, B > = if success { // TODO figure out a way to avoid the unwrap let value: A = value.try_into().unwrap(); Ok( value ) } else { // TODO figure out a way to avoid the unwrap let value: B = value.try_into().unwrap(); Err( value ) }; callback( value ); }; let state = js!( var callback = @{Once( callback )}; var state = { cancelled: false, callback: callback }; // TODO don't swallow any errors thrown inside callback @{self}.then( function ( value ) { if ( !state.cancelled ) { callback( value, true ); } }, function ( value ) { if ( !state.cancelled ) { callback( value, false ); } } ); return state; ); DiscardOnDrop::new( DoneHandle { state, } ) } /// This method converts the `Promise` into a [`PromiseFuture`](struct.PromiseFuture.html), so that it can be used as a Rust /// [`Future`](https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.5/futures/future/trait.Future.html). /// /// This method should rarely be needed, instead use [`value.try_into()`](unstable/trait.TryInto.html) to convert directly /// from a [`Value`](enum.Value.html) into a [`PromiseFuture`](struct.PromiseFuture.html). /// /// # Examples /// /// ```rust /// promise.to_future().map(|x| x + 1) /// ``` // We can't use the IntoFuture trait because Promise doesn't have a type argument // TODO explain more why we can't use the IntoFuture trait #[cfg(feature = "futures-support")] pub fn to_future< A, B >( &self ) -> PromiseFuture< A, B > where A: TryFrom< Value > + 'static, B: TryFrom< Value > + 'static, // TODO remove these later A::Error: std::fmt::Debug, B::Error: std::fmt::Debug { let ( sender, receiver ) = channel(); PromiseFuture { future: receiver, _done_handle: self.done( |value| { // TODO is this correct ? match sender.send( value ) { Ok( _ ) => {}, Err( _ ) => {}, }; } ), } } } /// A statically typed `Promise`. #[derive(Clone, Debug, PartialEq, Eq)] pub struct TypedPromise< T, E >( Promise, PhantomData< (T, E) > ); impl< T, E > TypedPromise< T, E > where T: TryFrom< Value >, E: TryFrom< Value > { #[inline] pub(crate) fn new( promise: Promise ) -> Self { TypedPromise( promise, PhantomData ) } /// A strongly typed version of [`Promise.done`](struct.Promise.html#method.done). #[inline] pub fn done< F >( &self, callback: F ) -> DiscardOnDrop< DoneHandle > where F: FnOnce( Result< T, E > ) + 'static, T::Error: fmt::Debug, E::Error: fmt::Debug { self.0.done( move |result| callback( result ) ) } } impl< T, E > From< TypedPromise< T, E > > for Promise { #[inline] fn from( promise: TypedPromise< T, E > ) -> Promise { promise.0 } }