diff --git a/Cargo.toml b/Cargo.toml index cf2347e4..c10f1b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,9 @@ build = "build.rs" discard = "1.0.3" serde = { version = "1", optional = true } serde_json = { version = "1", optional = true } -futures = { version = "0.1.18", optional = true } +futures-core = { version = "0.2.0", optional = true } +futures-channel = { version = "0.2.0", optional = true } +futures-util = { version = "0.2.0", optional = true } stdweb-derive = { version = "0.4", path = "stdweb-derive" } @@ -26,10 +28,11 @@ serde_json = "1" serde_derive = "1" [features] -default = ["serde", "serde_json", "futures"] +default = ["serde", "serde_json"] nightly = [] web_test = [] -experimental_features_which_may_break_on_minor_version_bumps = [] +futures-support = ["futures-core", "futures-channel", "futures-util"] +experimental_features_which_may_break_on_minor_version_bumps = ["futures-support"] "docs-rs" = [] [target.wasm32-unknown-unknown.dependencies] diff --git a/src/lib.rs b/src/lib.rs index 194c6042..e05a0657 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,8 +135,14 @@ extern crate stdweb_internal_macros; #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub use stdweb_internal_macros::js_export; -#[cfg(feature = "futures")] -extern crate futures; +#[cfg(feature = "futures-support")] +extern crate futures_core; + +#[cfg(feature = "futures-support")] +extern crate futures_util; + +#[cfg(feature = "futures-support")] +extern crate futures_channel; #[macro_use] extern crate stdweb_derive; @@ -181,7 +187,7 @@ pub use webcore::discard::DiscardOnDrop; pub use webcore::promise::{Promise, DoneHandle}; #[cfg(all( - feature = "futures", + feature = "futures-support", feature = "experimental_features_which_may_break_on_minor_version_bumps" ))] pub use webcore::promise_future::PromiseFuture; diff --git a/src/webcore/executor.rs b/src/webcore/executor.rs index 4ea66d24..fcb1b623 100644 --- a/src/webcore/executor.rs +++ b/src/webcore/executor.rs @@ -1,18 +1,15 @@ // This file implements a futures-compatible executor which schedules futures -// onto the JavaScript event loop. This implementation assumes there is a -// single thread and is *not* compatible with multiple WebAssembly workers sharing -// the same address space. +// onto the JavaScript event loop. // -// TODO: Implement support for multiple threads. This will require a mechanism to -// wake up another thread, such as the `postMessage` API. +// TODO: Verify that this works correctly for multiple threads. -use futures::future::{Future, ExecuteError, Executor}; -use futures::executor::{self, Notify, Spawn}; -use futures::Async; -use std::collections::VecDeque; -use std::result::Result as StdResult; -use std::cell::{Cell, RefCell}; +use futures_core::{Future, Async, Never}; +use futures_core::executor::{Executor, SpawnError}; +use futures_core::task::{LocalMap, Wake, Waker, Context}; use std::rc::Rc; +use std::cell::{Cell, RefCell}; +use std::sync::Arc; +use std::collections::VecDeque; use std::cmp; use webcore::try_from::TryInto; use webcore::value::Reference; @@ -21,89 +18,228 @@ use webcore::value::Reference; // TODO: Determine optimal values for these constants // Initial capacity of the event queue const INITIAL_QUEUE_CAPACITY: usize = 10; + // Iterations to wait before allowing the queue to shrink -const QUEUE_SHRINK_DELAY: usize = 25; +const QUEUE_SHRINK_DELAY: usize = 10; + +type BoxedFuture = Box< Future< Item = (), Error = Never > + 'static >; -// This functionality should really be in libstd, because the implementation -// looks stupid. -unsafe fn clone_raw< T >( ptr: *const T ) -> Rc< T > { - let result = Rc::from_raw( ptr ); - ::std::mem::forget( result.clone() ); - result +struct TaskInner { + map: LocalMap, + future: Option< BoxedFuture >, + executor: EventLoopExecutor, +} + +impl ::std::fmt::Debug for TaskInner { + fn fmt( &self, fmt: &mut ::std::fmt::Formatter ) -> Result< (), ::std::fmt::Error > { + fmt.debug_struct( "TaskInner" ) + .field( "map", &self.map ) + .field( "future", &self.future.as_ref().map( |_| "BoxedFuture" ) ) + .finish() + } } -// Typing this out is tedious -type BoxedFuture = Box< Future< Item = (), Error = () > + 'static >; -struct SpawnedTask { +// TODO is it possible to avoid the Mutex ? +#[derive(Debug)] +struct Task { is_queued: Cell< bool >, - spawn: RefCell< Option< Spawn< BoxedFuture > > >, + inner: RefCell< TaskInner >, } -impl SpawnedTask { - fn new< F >( future: F ) -> Rc< Self > - where F: Future< Item = (), Error = () > + 'static { - Rc::new( Self { - is_queued: Cell::new( false ), - spawn: RefCell::new( Some( executor::spawn( - Box::new( future ) as BoxedFuture - ) ) ) +// TODO fix these +unsafe impl Send for Task {} +unsafe impl Sync for Task {} + +impl Task { + fn new( executor: EventLoopExecutor, future: BoxedFuture ) -> Arc< Self > { + Arc::new( Self { + is_queued: Cell::new( true ), + inner: RefCell::new( TaskInner { + map: LocalMap::new(), + future: Some( future ), + executor, + } ), } ) } - fn poll( &self ) { - let mut spawn = self.spawn.borrow_mut(); + fn poll( arc: Arc< Self > ) { + let mut lock = arc.inner.borrow_mut(); + + // This is needed in order to borrow disjoint struct fields + let lock = &mut *lock; // Take the future so that if we panic it gets dropped - if let Some( mut spawn_future ) = spawn.take() { - // Clear `is_queued` flag so that it will re-queue if poll calls task.notify() - self.is_queued.set( false ); + if let Some( mut future ) = lock.future.take() { + // Clear `is_queued` flag so that it will re-queue if poll calls waker.wake() + arc.is_queued.set( false ); + + let poll = { + // TODO is there some way of saving these so they don't need to be recreated all the time ? + let waker = Waker::from( arc.clone() ); + + let mut cx = Context::new( &mut lock.map, &waker, &mut lock.executor ); - if spawn_future.poll_future_notify( &&EventLoop, self as *const _ as usize ) == Ok( Async::NotReady ) { + future.poll( &mut cx ) + }; + + if let Ok( Async::Pending ) = poll { // Future was not ready, so put it back - *spawn = Some( spawn_future ); + lock.future = Some( future ); + + // It was woken up during the poll, so we requeue it + if arc.is_queued.get() { + lock.executor.0.push_task( arc.clone() ); + } } } } - fn notify( task: Rc< SpawnedTask > ) { - // If not already queued - if !task.is_queued.replace( true ) { - EventLoop.push_task(task); + #[inline] + fn push_task( arc: &Arc< Self > ) { + if !arc.is_queued.replace( true ) { + if let Ok( lock ) = arc.inner.try_borrow() { + lock.executor.0.push_task( arc.clone() ); + } } } } -// A proxy for the JavaScript event loop. -struct EventLoop; +impl Wake for Task { + #[inline] + fn wake( arc: &Arc< Self > ) { + Task::push_task( arc ); + } +} -// There's only one thread, but this lets us tell the compiler that we -// don't need a `Sync` bound, and also gives us lazy initialization. -thread_local! { - static EVENT_LOOP_INNER: EventLoopInner = EventLoopInner::new(); + +#[derive(Debug)] +struct EventLoopInner { + // This avoids unnecessary allocations and interop overhead + // by using a Rust queue of pending tasks. + queue: VecDeque< Arc< Task > >, + // TODO handle overflow + past_sum: usize, + past_length: usize, + shrink_counter: usize, } -impl EventLoop { - fn drain(&self) { - EVENT_LOOP_INNER.with(EventLoopInner::drain) +#[derive(Debug)] +struct EventLoopQueue { + inner: RefCell< EventLoopInner >, + is_draining: Cell< bool >, +} + +impl EventLoopQueue { + // See if it's worth trying to reclaim some space from the queue + fn estimate_realloc_capacity( &self ) -> Option< ( usize, usize ) > { + let mut inner = self.inner.borrow_mut(); + + let cap = inner.queue.capacity(); + + inner.past_sum += inner.queue.len(); + inner.past_length += 1; + + let average = inner.past_sum / inner.past_length; + + // It will resize the queue if the average length is less than a quarter of the + // capacity. + // + // The check for INITIAL_QUEUE_CAPACITY is necessary in the situation + // where the queue is at its initial capacity, but the length is very low. + if average < cap / 4 && cap >= INITIAL_QUEUE_CAPACITY * 2 { + // It only resizes if the above condition is met for QUEUE_SHRINK_DELAY iterations. + inner.shrink_counter += 1; + + if inner.shrink_counter >= QUEUE_SHRINK_DELAY { + inner.shrink_counter = 0; + return Some( ( cap, cmp::max( average * 2, INITIAL_QUEUE_CAPACITY ) ) ); + } + + } else { + inner.shrink_counter = 0; + } + + None } - fn push_task(&self, task: Rc< SpawnedTask >) { - EVENT_LOOP_INNER.with(|inner| inner.push_task(task)) + + // Poll the queue until it is empty + fn drain( &self ) { + if !self.is_draining.replace( true ) { + let maybe_realloc_capacity = self.estimate_realloc_capacity(); + + // Poll all the pending tasks + loop { + let mut inner = self.inner.borrow_mut(); + + match inner.queue.pop_front() { + Some( task ) => { + // This is necessary because the polled task might queue more tasks + drop( inner ); + Task::poll( task ); + }, + None => { + // We decided to reclaim some space + if let Some( ( old_capacity, realloc_capacity ) ) = maybe_realloc_capacity { + inner.queue = VecDeque::with_capacity( realloc_capacity ); + + let new_capacity = inner.queue.capacity(); + + // This makes sure that we are actually shrinking the capacity + assert!( new_capacity < old_capacity ); + + // This is necessary because the estimate_realloc_capacity method + // relies upon the behavior of the VecDeque's capacity + assert!( new_capacity < realloc_capacity * 2 ); + } + + self.is_draining.set( false ); + + break; + }, + } + } + } } } -// State relating to the JavaScript event loop. Only one instance ever exists. -struct EventLoopInner { - // Avoid unnecessary allocation and interop by keeping a local - // queue of pending tasks. - microtask_queue: RefCell< VecDeque< Rc< SpawnedTask > > >, + +// A proxy for the JavaScript event loop. +#[derive(Debug)] +struct EventLoop { + queue: Rc< EventLoopQueue >, + // TODO is this thread-safe ? waker: Reference, - shrink_counter: Cell } -// Not strictly necessary, but may become relevant in the future -impl Drop for EventLoopInner { +impl EventLoop { + // Waits for next microtask tick + fn queue_microtask( &self ) { + js! { @(no_return) @{&self.waker}(); } + } + + // Pushes a task onto the queue + fn push_task( &self, task: Arc< Task > ) { + let mut inner = self.queue.inner.borrow_mut(); + + inner.queue.push_back( task ); + + // If the queue was previously empty, then we need to schedule + // the queue to be drained. + // + // The check for `is_draining` is necessary in the situation where + // the `drain` method pops the last task from the queue, but that + // task then re-queues another task. + if inner.queue.len() == 1 && !self.queue.is_draining.get() { + self.queue_microtask(); + } + } +} + +// Not currently necessary, but may become relevant in the future +// TODO what about when the thread is killed, is this guaranteed to be called ? +impl Drop for EventLoop { #[inline] fn drop( &mut self ) { js! { @(no_return) @@ -112,16 +248,42 @@ impl Drop for EventLoopInner { } } -impl EventLoopInner { - // Initializes the event loop. Only called once. + +#[derive(Debug, Clone)] +struct EventLoopExecutor( Rc< EventLoop > ); + +impl EventLoopExecutor { fn new() -> Self { - EventLoopInner { - microtask_queue: RefCell::new(VecDeque::with_capacity(INITIAL_QUEUE_CAPACITY)), - waker: js!( - var callback = @{|| EventLoop.drain()}; - var wrapper = function() { - if (!callback.dropped) { callback() } - }; + let queue = VecDeque::with_capacity( INITIAL_QUEUE_CAPACITY ); + // This is necessary because the estimate_realloc_capacity method + // relies upon the behavior of the VecDeque's capacity + assert!( queue.capacity() < INITIAL_QUEUE_CAPACITY * 2 ); + + let queue = Rc::new( EventLoopQueue { + inner: RefCell::new( EventLoopInner { + queue: queue, + past_sum: 0, + past_length: 0, + shrink_counter: 0, + } ), + + is_draining: Cell::new( false ), + } ); + + let waker = { + let queue = queue.clone(); + + js!( + var callback = @{move || queue.drain()}; + + var dropped = false; + + function wrapper() { + if ( !dropped ) { + callback(); + } + } + var nextTick; // Modern browsers can use `MutationObserver` which allows @@ -133,7 +295,7 @@ impl EventLoopInner { new MutationObserver( wrapper ).observe( node, { characterData: true } ); - nextTick = function() { + nextTick = function () { state = !state; node.data = ( state ? "1" : "0" ); }; @@ -142,102 +304,43 @@ impl EventLoopInner { } else { var promise = Promise.resolve( null ); - nextTick = function() { + nextTick = function () { promise.then( wrapper ); }; } - nextTick.drop = function() { - callback.dropped = true; + nextTick.drop = function () { + dropped = true; callback.drop(); }; return nextTick; - ).try_into().unwrap(), - shrink_counter: Cell::new(0) - } - } - // Pushes a task onto the queue - fn push_task(&self, task: Rc< SpawnedTask >) { - let mut queue = self.microtask_queue.borrow_mut(); - queue.push_back(task); + ).try_into().unwrap() + }; - // If the queue was previously empty, then we need to schedule - // the queue to be drained. - if queue.len() == 1 { - self.wake(); - } - } - // Invoke the JavaScript waker function - fn wake(&self) { - js! { @(no_return) @{&self.waker}(); } - } - // Remove and return a task from the front of the queue - fn pop_task(&self) -> Option< Rc< SpawnedTask > > { - self.microtask_queue.borrow_mut().pop_front() - } - // See if it's worth trying to reclaim some space from the queue - fn estimate_realloc_capacity(&self) -> Option { - let queue = self.microtask_queue.borrow(); - // A VecDeque retains a `2^n-1` capacity - let half_cap = queue.capacity()/2; - // We consider shrinking the queue if it is less than - // half full... - if half_cap > queue.len() && half_cap > INITIAL_QUEUE_CAPACITY { - // ...and if it's been that way for at least - // `QUEUE_SHRINK_DELAY` iterations. - let shrink_counter = self.shrink_counter.get(); - if shrink_counter < QUEUE_SHRINK_DELAY { - self.shrink_counter.set(shrink_counter + 1); - } else { - self.shrink_counter.set(0); - return Some(cmp::max(queue.len(), INITIAL_QUEUE_CAPACITY)); - } - } else { - self.shrink_counter.set(0); - } - None + EventLoopExecutor( Rc::new( EventLoop { queue, waker } ) ) } - // Poll the queue until it is empty - fn drain(&self) { - let maybe_realloc_capacity = self.estimate_realloc_capacity(); - - // Poll all the pending tasks - while let Some(task) = self.pop_task() { - task.poll(); - } - if let Some(realloc_capacity) = maybe_realloc_capacity { - // We decided to reclaim some space - *self.microtask_queue.borrow_mut() = VecDeque::with_capacity(realloc_capacity); - } + #[inline] + fn spawn_local( &self, future: BoxedFuture ) { + self.0.push_task( Task::new( self.clone(), future ) ); } } -impl< F > Executor< F > for EventLoop where - F: Future< Item = (), Error = () > + 'static { - fn execute( &self, future: F ) -> StdResult< (), ExecuteError< F > > { - SpawnedTask::notify( SpawnedTask::new( future ) ); +impl Executor for EventLoopExecutor { + #[inline] + fn spawn( &mut self, f: Box< Future< Item = (), Error = Never > + Send + 'static > ) -> Result< (), SpawnError > { + self.spawn_local( f ); Ok( () ) } } -impl Notify for EventLoop { - fn notify( &self, spawned_id: usize ) { - SpawnedTask::notify( unsafe { clone_raw( spawned_id as *const _ ) } ); - } - - fn clone_id( &self, id: usize ) -> usize { - unsafe { Rc::into_raw( clone_raw( id as *const SpawnedTask ) ) as usize } - } - fn drop_id( &self, id: usize ) { - unsafe { Rc::from_raw( id as *const SpawnedTask ) }; - } +thread_local! { + static EVENT_LOOP: EventLoopExecutor = EventLoopExecutor::new(); } #[inline] -pub fn spawn< F >( future: F ) where - F: Future< Item = (), Error = () > + 'static { - EventLoop.execute( future ).unwrap(); +pub fn spawn_local< F >( future: F ) where F: Future< Item = (), Error = Never > + 'static { + EVENT_LOOP.with( |event_loop| event_loop.spawn_local( Box::new( future ) ) ) } diff --git a/src/webcore/mod.rs b/src/webcore/mod.rs index ce20700a..e747f004 100644 --- a/src/webcore/mod.rs +++ b/src/webcore/mod.rs @@ -19,10 +19,10 @@ pub mod reference_type; pub mod promise; pub mod discard; -#[cfg(feature = "futures")] +#[cfg(feature = "futures-support")] pub mod promise_future; -#[cfg(feature = "futures")] +#[cfg(feature = "futures-support")] pub mod executor; #[cfg(feature = "nightly")] diff --git a/src/webcore/promise.rs b/src/webcore/promise.rs index ce10b5b2..f51a9538 100644 --- a/src/webcore/promise.rs +++ b/src/webcore/promise.rs @@ -5,16 +5,19 @@ use webcore::value::{Value, Reference}; use webcore::try_from::{TryInto, TryFrom}; use webcore::discard::DiscardOnDrop; -#[cfg(feature = "futures")] +#[cfg(feature = "futures-support")] use webcore::serialization::JsSerialize; -#[cfg(feature = "futures")] -use futures::unsync::oneshot::channel; +#[cfg(feature = "futures-support")] +use futures_channel::oneshot::channel; -#[cfg(feature = "futures")] -use futures::future::Future; +#[cfg(feature = "futures-support")] +use futures_core::IntoFuture; -#[cfg(feature = "futures")] +#[cfg(feature = "futures-support")] +use futures_util::FutureExt; + +#[cfg(feature = "futures-support")] use super::promise_future::PromiseFuture; @@ -103,7 +106,7 @@ impl Promise { /// 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) and the - /// [`Future`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html) + /// [`FutureExt`](https://docs.rs/futures/0.2.*/futures/future/trait.FutureExt.html) /// methods instead. /// /// # Examples @@ -126,19 +129,22 @@ impl Promise { /// /// [(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")] + #[cfg(feature = "futures-support")] pub fn from_future< A >( future: A ) -> Self - where A: Future + 'static, + where A: IntoFuture, + A::Future: 'static, A::Item: 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| { - PromiseFuture::spawn( + PromiseFuture::spawn_local( future.then( move |result| { match result { Ok( a ) => call( success, a ), @@ -257,9 +263,9 @@ impl Promise { } ) } - /// 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). + /// This method converts the `Promise` into a [`PromiseFuture`](struct.PromiseFuture.html), so that it can be used as a Rust [`Future`](https://docs.rs/futures/0.2.*/futures/future/trait.Future.html). /// - /// This method converts the `Promise` into a [`PromiseFuture`](struct.PromiseFuture.html), so that it can be used as a Rust [`Future`](https://docs.rs/futures/0.1.*/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 /// @@ -268,7 +274,7 @@ impl Promise { /// ``` // 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")] + #[cfg(feature = "futures-support")] pub fn to_future< A, B >( &self ) -> PromiseFuture< A, B > where A: TryFrom< Value > + 'static, B: TryFrom< Value > + 'static, diff --git a/src/webcore/promise_future.rs b/src/webcore/promise_future.rs index 76ded0f4..650b7632 100644 --- a/src/webcore/promise_future.rs +++ b/src/webcore/promise_future.rs @@ -2,14 +2,16 @@ use std; use webcore::value::{Value, ConversionError}; use webcore::try_from::{TryInto, TryFrom}; use webapi::error; -use futures::{Future, Poll, Async}; -use futures::unsync::oneshot::Receiver; -use webcore::executor::spawn; +use futures_core::{Future, Poll, Async, Never}; +use futures_core::task::Context; +use futures_channel::oneshot::Receiver; +use webcore::executor::spawn_local; use webcore::discard::DiscardOnDrop; +use webcore::serialization::JsSerialize; use super::promise::{Promise, DoneHandle}; -/// This allows you to use a JavaScript [`Promise`](struct.Promise.html) as if it is a Rust [`Future`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html). +/// This allows you to use a JavaScript [`Promise`](struct.Promise.html) as if it is a Rust [`Future`](https://docs.rs/futures/0.2.*/futures/future/trait.Future.html). /// /// The preferred way to create a `PromiseFuture` is to use [`value.try_into()`](unstable/trait.TryInto.html) on a JavaScript [`Value`](enum.Value.html). /// @@ -25,20 +27,22 @@ pub struct PromiseFuture< Value, Error = error::Error > { pub(crate) _done_handle: DiscardOnDrop< DoneHandle >, } -impl PromiseFuture< (), () > { - /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html) and then immediately returns. - /// This does not block the current thread. The only way to retrieve the value of the future is to use the various - /// [`Future`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html) methods, such as - /// [`map`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html#method.map) or - /// [`inspect`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html#method.inspect). +impl PromiseFuture< (), Never > { + /// Asynchronously runs the [`Future`](https://docs.rs/futures/0.2.*/futures/future/trait.Future.html) on the current thread + /// and then immediately returns. This does *not* block the current thread. /// - /// This function requires you to handle all errors yourself. Because the errors happen asynchronously, the only way to catch them is - /// to use a [`Future`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html) method, such as - /// [`map_err`](https://docs.rs/futures/0.1.*/futures/future/trait.Future.html#method.map_err). + /// This function is normally called once in `main`, it is usually not needed to call it multiple times. /// - /// It is very common to want to print the errors to the console. You can do that by using `.map_err(|e| console!(error, e))` + /// The only way to retrieve the value of the future is to use the various + /// [`FutureExt`](https://docs.rs/futures/0.2.*/futures/future/trait.FutureExt.html) methods, such as + /// [`map`](https://docs.rs/futures/0.2.*/futures/future/trait.FutureExt.html#method.map) or + /// [`inspect`](https://docs.rs/futures/0.2.*/futures/future/trait.FutureExt.html#method.inspect). /// - /// This function is normally called once in `main`, it is usually not needed to call it multiple times. + /// In addition, you must handle all errors yourself. Because the errors happen asynchronously, the only way to catch them is + /// to use a [`FutureExt`](https://docs.rs/futures/0.2.*/futures/future/trait.FutureExt.html) method, such as + /// [`map_err`](https://docs.rs/futures/0.2.*/futures/future/trait.FutureExt.html#method.map_err). + /// + /// It is very common to want to print the errors to the console. You can do that by using `.map_err(PromiseFuture::print_error_panic)` /// /// # Examples /// @@ -46,21 +50,23 @@ impl PromiseFuture< (), () > { /// /// ```rust /// fn main() { - /// PromiseFuture::spawn( + /// PromiseFuture::spawn_local( /// create_some_future() - /// .map_err(|e| console!(error, e)) + /// .map_err(PromiseFuture::print_error_panic) /// ); /// } /// ``` /// - /// Inspect the output value of the future: + /// Use the output value of the future: /// /// ```rust /// fn main() { - /// PromiseFuture::spawn( + /// PromiseFuture::spawn_local( /// create_some_future() - /// .inspect(|x| println!("Future finished: {:#?}", x)) - /// .map_err(|e| console!(error, e)) + /// .map(|x| { + /// println!("Future finished with value: {:#?}", x); + /// }) + /// .map_err(PromiseFuture::print_error_panic) /// ); /// } /// ``` @@ -69,22 +75,36 @@ impl PromiseFuture< (), () > { /// /// ```rust /// fn main() { - /// PromiseFuture::spawn( + /// PromiseFuture::spawn_local( /// create_some_future() /// .map_err(|e| handle_error_somehow(e)) /// ); /// } /// ``` #[inline] - pub fn spawn< B >( future: B ) where - B: Future< Item = (), Error = () > + 'static { - spawn( future ); + pub fn spawn_local< B >( future: B ) where + B: Future< Item = (), Error = Never > + 'static { + spawn_local( future ); + } + + /// Prints an error to the console and then panics. + /// + /// See the documentation for [`spawn_local`](#method.spawn_local) for more details. + /// + /// # Panics + /// This function *always* panics. + #[inline] + pub fn print_error_panic< A: JsSerialize >( value: A ) -> Never { + js! { @(no_return) + console.error( @{value} ); + } + panic!(); } } impl< A, B > std::fmt::Debug for PromiseFuture< A, B > { fn fmt( &self, formatter: &mut std::fmt::Formatter ) -> std::fmt::Result { - write!( formatter, "PromiseFuture" ) + formatter.debug_struct( "PromiseFuture" ).finish() } } @@ -92,12 +112,12 @@ impl< A, B > Future for PromiseFuture< A, B > { type Item = A; type Error = B; - fn poll( &mut self ) -> Poll< Self::Item, Self::Error > { + fn poll( &mut self, cx: &mut Context ) -> Poll< Self::Item, Self::Error > { // TODO maybe remove this unwrap ? - match self.future.poll().unwrap() { + match self.future.poll( cx ).unwrap() { Async::Ready( Ok( a ) ) => Ok( Async::Ready( a ) ), Async::Ready( Err( e ) ) => Err( e ), - Async::NotReady => Ok( Async::NotReady ), + Async::Pending => Ok( Async::Pending ), } } }