diff --git a/Cargo.toml b/Cargo.toml index a0a2c035189..c5730add5cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ cfg-if = "0.1.9" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] js-sys = { path = 'crates/js-sys', version = '0.3.27' } -wasm-bindgen-test = { path = 'crates/test', version = '=0.2.50' } +wasm-bindgen-test = { path = 'crates/test', version = '=0.3.0' } serde_derive = "1.0" wasm-bindgen-test-crate-a = { path = 'tests/crates/a', version = '0.1' } wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 539ffb99249..fe7aa1e2cda 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,6 +9,8 @@ jobs: displayName: "Run wasm-bindgen crate tests (unix)" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -48,6 +50,8 @@ jobs: vmImage: vs2017-win2016 steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -91,6 +95,8 @@ jobs: displayName: "Run web-sys crate tests" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -104,6 +110,8 @@ jobs: displayName: "Run js-sys crate tests" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-geckodriver.yml - template: ci/azure-install-sccache.yml @@ -113,6 +121,8 @@ jobs: displayName: "Run wasm-bindgen-webidl crate tests" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-node.yml #- template: ci/azure-install-sccache.yml - script: cargo test -p wasm-bindgen-webidl @@ -125,7 +135,7 @@ jobs: steps: - template: ci/azure-install-rust.yml parameters: - toolchain: beta + toolchain: nightly - template: ci/azure-install-node.yml - template: ci/azure-install-sccache.yml - script: cargo test -p wasm-bindgen-macro @@ -156,6 +166,8 @@ jobs: displayName: "Build almost all examples" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-sccache.yml - template: ci/azure-install-wasm-pack.yml - script: mv _package.json package.json && npm install && rm package.json @@ -198,6 +210,8 @@ jobs: displayName: "Build benchmarks" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly - template: ci/azure-install-sccache.yml - template: ci/azure-install-wasm-pack.yml - script: wasm-pack build --target web benchmarks @@ -263,7 +277,6 @@ jobs: - job: doc_book displayName: "Doc - build the book" steps: - - template: ci/azure-install-rust.yml - script: | set -e curl -L https://github.com/rust-lang-nursery/mdBook/releases/download/v0.3.0/mdbook-v0.3.0-x86_64-unknown-linux-gnu.tar.gz | tar xzf - @@ -279,6 +292,8 @@ jobs: displayName: "Doc - build the API documentation" steps: - template: ci/azure-install-rust.yml + parameters: + toolchain: nightly # Install rustfmt so we can format the web-sys bindings - script: rustup component add rustfmt displayName: "Install rustfmt" diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml index a0d99c2a788..28640617d52 100644 --- a/crates/futures/Cargo.toml +++ b/crates/futures/Cargo.toml @@ -7,17 +7,13 @@ license = "MIT/Apache-2.0" name = "wasm-bindgen-futures" repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures" readme = "./README.md" -version = "0.3.27" +version = "0.4.0" edition = "2018" [dependencies] cfg-if = "0.1.9" -futures = "0.1.20" js-sys = { path = "../js-sys", version = '0.3.27' } wasm-bindgen = { path = "../..", version = '0.2.50' } -futures-util-preview = { version = "0.3.0-alpha.18", optional = true } -futures-channel-preview = { version = "0.3.0-alpha.18", optional = true } -lazy_static = { version = "1.3.0", optional = true } [target.'cfg(target_feature = "atomics")'.dependencies.web-sys] path = "../web-sys" @@ -28,7 +24,4 @@ features = [ ] [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = { path = '../test', version = '0.2.50' } - -[features] -futures_0_3 = ["futures-util-preview", "futures-channel-preview", "lazy_static"] +wasm-bindgen-test = { path = '../test', version = '0.3.0' } diff --git a/crates/futures/src/futures_0_3.rs b/crates/futures/src/futures_0_3.rs deleted file mode 100644 index 79fe46cd63b..00000000000 --- a/crates/futures/src/futures_0_3.rs +++ /dev/null @@ -1,274 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::collections::VecDeque; -use std::fmt; -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::sync::Arc; -use std::task::{Context, Poll}; - -use futures_channel::oneshot; -use futures_util::future::FutureExt; -use futures_util::task::ArcWake; - -use lazy_static::lazy_static; - -use js_sys::Promise; -use wasm_bindgen::prelude::*; - -/// A Rust `Future` backed by a JavaScript `Promise`. -/// -/// This type is constructed with a JavaScript `Promise` object and translates -/// it to a Rust `Future`. This type implements the `Future` trait from the -/// `futures` crate and will either succeed or fail depending on what happens -/// with the JavaScript `Promise`. -/// -/// Currently this type is constructed with `JsFuture::from`. -pub struct JsFuture { - rx: oneshot::Receiver>, -} - -impl fmt::Debug for JsFuture { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "JsFuture {{ ... }}") - } -} - -impl From for JsFuture { - fn from(js: Promise) -> JsFuture { - // See comments in `src/lib.rs` for why we're using one self-contained - // callback here. - let (tx, rx) = oneshot::channel(); - let state = Rc::new(RefCell::new(None)); - let state2 = state.clone(); - let resolve = Closure::once(move |val| finish(&state2, Ok(val))); - let state2 = state.clone(); - let reject = Closure::once(move |val| finish(&state2, Err(val))); - - js.then2(&resolve, &reject); - *state.borrow_mut() = Some((tx, resolve, reject)); - - return JsFuture { rx }; - - fn finish( - state: &RefCell< - Option<( - oneshot::Sender>, - Closure, - Closure, - )>, - >, - val: Result, - ) { - match state.borrow_mut().take() { - // We don't have any guarantee that anyone's still listening at this - // point (the Rust `JsFuture` could have been dropped) so simply - // ignore any errors here. - Some((tx, _, _)) => drop(tx.send(val)), - None => wasm_bindgen::throw_str("cannot finish twice"), - } - } - } -} - -impl Future for JsFuture { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - match self.rx.poll_unpin(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Ok(val)) => Poll::Ready(val), - Poll::Ready(Err(_)) => wasm_bindgen::throw_str("cannot cancel"), - } - } -} - -/// Converts a Rust `Future` into a JavaScript `Promise`. -/// -/// This function will take any future in Rust and schedule it to be executed, -/// returning a JavaScript `Promise` which can then be passed back to JavaScript -/// to get plumbed into the rest of a system. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. The -/// returned `Promise` will be resolved or rejected when the future completes, -/// depending on whether it finishes with `Ok` or `Err`. -/// -/// # Panics -/// -/// Note that in wasm panics are currently translated to aborts, but "abort" in -/// this case means that a JavaScript exception is thrown. The wasm module is -/// still usable (likely erroneously) after Rust panics. -/// -/// If the `future` provided panics then the returned `Promise` **will not -/// resolve**. Instead it will be a leaked promise. This is an unfortunate -/// limitation of wasm currently that's hoped to be fixed one day! -pub fn future_to_promise(future: F) -> Promise -where - F: Future> + 'static, -{ - let mut future = Some(future); - - Promise::new(&mut |resolve, reject| { - // TODO change Promise::new to be FnOnce - spawn_local(future.take().unwrap_throw().map(move |val| match val { - Ok(val) => { - resolve.call1(&JsValue::undefined(), &val).unwrap_throw(); - } - Err(val) => { - reject.call1(&JsValue::undefined(), &val).unwrap_throw(); - } - })); - }) -} - -/// Runs a Rust `Future` on a local task queue. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. -/// -/// # Panics -/// -/// This function has the same panic behavior as `future_to_promise`. -pub fn spawn_local(future: F) -where - F: Future + 'static, -{ - struct Task { - // This is an Option so that the Future can be immediately dropped when it's finished - future: RefCell + 'static>>>>, - - // This is used to ensure that the Task will only be queued once - is_queued: Cell, - } - - // TODO This is only safe because JS is currently single-threaded - unsafe impl Send for Task {} - unsafe impl Sync for Task {} - - impl Task { - #[inline] - fn new(future: F) -> Arc - where - F: Future + 'static, - { - Arc::new(Self { - future: RefCell::new(Some(Box::pin(future))), - is_queued: Cell::new(false), - }) - } - } - - impl ArcWake for Task { - fn wake_by_ref(arc_self: &Arc) { - // This ensures that it's only queued once - if arc_self.is_queued.replace(true) { - return; - } - - let mut lock = EXECUTOR.tasks.borrow_mut(); - - lock.push_back(arc_self.clone()); - - // The Task will be polled on the next microtask event tick - EXECUTOR.next_tick.schedule(); - } - } - - struct NextTick { - is_spinning: Cell, - promise: Promise, - closure: Closure, - } - - impl NextTick { - #[inline] - fn new(mut f: F) -> Self - where - F: FnMut() + 'static, - { - Self { - is_spinning: Cell::new(false), - promise: Promise::resolve(&JsValue::null()), - closure: Closure::wrap(Box::new(move |_| { - f(); - })), - } - } - - fn schedule(&self) { - // This ensures that it's only scheduled once - if self.is_spinning.replace(true) { - return; - } - - // TODO avoid creating a new Promise - self.promise.then(&self.closure); - } - - fn done(&self) { - self.is_spinning.set(false); - } - } - - struct Executor { - // This is a queue of Tasks which will be polled in order - tasks: RefCell>>, - - // This is used to ensure that Tasks are polled on the next microtask event tick - next_tick: NextTick, - } - - // TODO This is only safe because JS is currently single-threaded - unsafe impl Send for Executor {} - unsafe impl Sync for Executor {} - - lazy_static! { - static ref EXECUTOR: Executor = Executor { - tasks: RefCell::new(VecDeque::new()), - - // This closure will only be called on the next microtask event tick - next_tick: NextTick::new(|| { - let tasks = &EXECUTOR.tasks; - - loop { - let mut lock = tasks.borrow_mut(); - - match lock.pop_front() { - Some(task) => { - let mut borrow = task.future.borrow_mut(); - - // This will only be None if the Future wakes up the Waker after returning Poll::Ready - if let Some(future) = borrow.as_mut() { - let poll = { - // Clear `is_queued` flag so that it will re-queue if poll calls waker.wake() - task.is_queued.set(false); - - // This is necessary because the polled task might queue more tasks - drop(lock); - - // TODO is there some way of saving these so they don't need to be recreated all the time ? - let waker = futures_util::task::waker(task.clone()); - let cx = &mut Context::from_waker(&waker); - Pin::new(future).poll(cx) - }; - - if let Poll::Ready(_) = poll { - // Cleanup the Future immediately - *borrow = None; - } - } - }, - None => { - // All of the Tasks have been polled, so it's now possible to schedule the NextTick again - EXECUTOR.next_tick.done(); - break; - }, - } - } - }), - }; - } - - ArcWake::wake_by_ref(&Task::new(future)); -} diff --git a/crates/futures/src/legacy.rs b/crates/futures/src/legacy.rs deleted file mode 100644 index a200d088c60..00000000000 --- a/crates/futures/src/legacy.rs +++ /dev/null @@ -1,204 +0,0 @@ -use futures::executor::{self, Notify, Spawn}; -use futures::prelude::*; -use js_sys::{Function, Promise}; -use std::cell::{Cell, RefCell}; -use std::rc::Rc; -use std::sync::Arc; -use wasm_bindgen::prelude::*; - -/// Converts a Rust `Future` into a JavaScript `Promise`. -/// -/// This function will take any future in Rust and schedule it to be executed, -/// returning a JavaScript `Promise` which can then be passed back to JavaScript -/// to get plumbed into the rest of a system. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. The -/// returned `Promise` will be resolved or rejected when the future completes, -/// depending on whether it finishes with `Ok` or `Err`. -/// -/// # Panics -/// -/// Note that in wasm panics are currently translated to aborts, but "abort" in -/// this case means that a JavaScript exception is thrown. The wasm module is -/// still usable (likely erroneously) after Rust panics. -/// -/// If the `future` provided panics then the returned `Promise` **will not -/// resolve**. Instead it will be a leaked promise. This is an unfortunate -/// limitation of wasm currently that's hoped to be fixed one day! -pub fn future_to_promise(future: F) -> Promise -where - F: Future + 'static, -{ - _future_to_promise(Box::new(future)) -} - -// Implementation of actually transforming a future into a JavaScript `Promise`. -// -// The only primitive we have to work with here is `Promise::new`, which gives -// us two callbacks that we can use to either reject or resolve the promise. -// It's our job to ensure that one of those callbacks is called at the -// appropriate time. -// -// Now we know that JavaScript (in general) can't block and is largely -// notification/callback driven. That means that our future must either have -// synchronous computational work to do, or it's "scheduled a notification" to -// happen. These notifications are likely callbacks to get executed when things -// finish (like a different promise or something like `setTimeout`). The general -// idea here is thus to do as much synchronous work as we can and then otherwise -// translate notifications of a future's task into "let's poll the future!" -// -// This isn't necessarily the greatest future executor in the world, but it -// should get the job done for now hopefully. -fn _future_to_promise(future: Box>) -> Promise { - let mut future = Some(executor::spawn(future)); - return Promise::new(&mut |resolve, reject| { - Package::poll(&Arc::new(Package { - spawn: RefCell::new(future.take().unwrap()), - resolve, - reject, - notified: Cell::new(State::Notified), - })); - }); - - struct Package { - // Our "spawned future". This'll have everything we need to poll the - // future and continue to move it forward. - spawn: RefCell>>>, - - // The current state of this future, expressed in an enum below. This - // indicates whether we're currently polling the future, received a - // notification and need to keep polling, or if we're waiting for a - // notification to come in (and no one is polling). - notified: Cell, - - // Our two callbacks connected to the `Promise` that we returned to - // JavaScript. We'll be invoking one of these at the end. - resolve: Function, - reject: Function, - } - - // The possible states our `Package` (future) can be in, tracked internally - // and used to guide what happens when polling a future. - enum State { - // This future is currently and actively being polled. Attempting to - // access the future will result in a runtime panic and is considered a - // bug. - Polling, - - // This future has been notified, while it was being polled. This marker - // is used in the `Notify` implementation below, and indicates that a - // notification was received that the future is ready to make progress. - // If seen, however, it probably means that the future is also currently - // being polled. - Notified, - - // The future is blocked, waiting for something to happen. Stored here - // is a self-reference to the future itself so we can pull it out in - // `Notify` and continue polling. - // - // Note that the self-reference here is an Arc-cycle that will leak - // memory unless the future completes, but currently that should be ok - // as we'll have to stick around anyway while the future is executing! - // - // This state is removed as soon as a notification comes in, so the leak - // should only be "temporary" - Waiting(Arc), - } - - // No shared memory right now, wasm is single threaded, no need to worry - // about this! - unsafe impl Send for Package {} - unsafe impl Sync for Package {} - - impl Package { - // Move the future contained in `me` as far forward as we can. This will - // do as much synchronous work as possible to complete the future, - // ensuring that when it blocks we're scheduled to get notified via some - // callback somewhere at some point (vague, right?) - // - // TODO: this probably shouldn't do as much synchronous work as possible - // as it can starve other computations. Rather it should instead - // yield every so often with something like `setTimeout` with the - // timeout set to zero. - fn poll(me: &Arc) { - loop { - match me.notified.replace(State::Polling) { - // We received a notification while previously polling, or - // this is the initial poll. We've got work to do below! - State::Notified => {} - - // We've gone through this loop once and no notification was - // received while we were executing work. That means we got - // `NotReady` below and we're scheduled to receive a - // notification. Block ourselves and wait for later. - // - // When the notification comes in it'll notify our task, see - // our `Waiting` state, and resume the polling process - State::Polling => { - me.notified.set(State::Waiting(me.clone())); - break; - } - - State::Waiting(_) => panic!("shouldn't see waiting state!"), - } - - let (val, f) = match me.spawn.borrow_mut().poll_future_notify(me, 0) { - // If the future is ready, immediately call the - // resolve/reject callback and then return as we're done. - Ok(Async::Ready(value)) => (value, &me.resolve), - Err(value) => (value, &me.reject), - - // Otherwise keep going in our loop, if we weren't notified - // we'll break out and start waiting. - Ok(Async::NotReady) => continue, - }; - - drop(f.call1(&JsValue::undefined(), &val)); - break; - } - } - } - - impl Notify for Package { - fn notify(&self, _id: usize) { - let me = match self.notified.replace(State::Notified) { - // we need to schedule polling to resume, so keep going - State::Waiting(me) => me, - - // we were already notified, and were just notified again; - // having now coalesced the notifications we return as it's - // still someone else's job to process this - State::Notified => return, - - // the future was previously being polled, and we've just - // switched it to the "you're notified" state. We don't have - // access to the future as it's being polled, so the future - // polling process later sees this notification and will - // continue polling. For us, though, there's nothing else to do, - // so we bail out. - // later see - State::Polling => return, - }; - - // Use `Promise.then` on a resolved promise to place our execution - // onto the next turn of the microtask queue, enqueueing our poll - // operation. We don't currently poll immediately as it turns out - // `futures` crate adapters aren't compatible with it and it also - // helps avoid blowing the stack by accident. - // - // Note that the `Rc`/`RefCell` trick here is basically to just - // ensure that our `Closure` gets cleaned up appropriately. - let promise = Promise::resolve(&JsValue::undefined()); - let slot = Rc::new(RefCell::new(None)); - let slot2 = slot.clone(); - let closure = Closure::wrap(Box::new(move |_| { - let myself = slot2.borrow_mut().take(); - debug_assert!(myself.is_some()); - Package::poll(&me); - }) as Box); - promise.then(&closure); - *slot.borrow_mut() = Some(closure); - } - } -} diff --git a/crates/futures/src/legacy_atomics.rs b/crates/futures/src/legacy_atomics.rs deleted file mode 100644 index b09e8b7ced5..00000000000 --- a/crates/futures/src/legacy_atomics.rs +++ /dev/null @@ -1,166 +0,0 @@ -use futures::executor::{self, Notify, Spawn}; -use futures::prelude::*; -use js_sys::Function; -use std::sync::atomic::{AtomicI32, Ordering}; -use std::sync::Arc; -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; - -// Duplicate a bit here because `then` takes a `JsValue` instead of a `Closure`. -#[wasm_bindgen] -extern "C" { - type Promise; - #[wasm_bindgen(method)] - fn then(this: &Promise, cb: &JsValue) -> Promise; - - type Atomics; - #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync)] - fn wait_async(buf: &JsValue, index: i32, value: i32) -> js_sys::Promise; - #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync, getter)] - fn get_wait_async() -> JsValue; -} - -/// Converts a Rust `Future` into a JavaScript `Promise`. -/// -/// This function will take any future in Rust and schedule it to be executed, -/// returning a JavaScript `Promise` which can then be passed back to JavaScript -/// to get plumbed into the rest of a system. -/// -/// The `future` provided must adhere to `'static` because it'll be scheduled -/// to run in the background and cannot contain any stack references. The -/// returned `Promise` will be resolved or rejected when the future completes, -/// depending on whether it finishes with `Ok` or `Err`. -/// -/// # Panics -/// -/// Note that in wasm panics are currently translated to aborts, but "abort" in -/// this case means that a JavaScript exception is thrown. The wasm module is -/// still usable (likely erroneously) after Rust panics. -/// -/// If the `future` provided panics then the returned `Promise` **will not -/// resolve**. Instead it will be a leaked promise. This is an unfortunate -/// limitation of wasm currently that's hoped to be fixed one day! -pub fn future_to_promise(future: F) -> js_sys::Promise -where - F: Future + 'static, -{ - _future_to_promise(Box::new(future)) -} - -// Implementation of actually transforming a future into a JavaScript `Promise`. -// -// The main primitives used here are `Promise::new` to actually create a JS -// promise to return as well as `Atomics.waitAsync` to create a promise that we -// can asynchronously wait on. The general idea here is that we'll create a -// promise to return and schedule work to happen in `Atomics.waitAsync` -// callbacks. -// -// After we've created a promise we start polling a future, and whenever it's -// not ready we'll execute `Atomics.waitAsync`. When that resolves we'll keep -// polling the future, and this happens until the future is done. Finally -// when it's all finished we call either resolver or reject depending on the -// result of the future. -fn _future_to_promise(future: Box>) -> js_sys::Promise { - let mut future = Some(executor::spawn(future)); - return js_sys::Promise::new(&mut |resolve, reject| { - Package { - spawn: future.take().unwrap(), - resolve, - reject, - waker: Arc::new(Waker { - value: AtomicI32::new(1), // 1 == "notified, ready to poll" - }), - } - .poll(); - }); - - struct Package { - // Our "spawned future". This'll have everything we need to poll the - // future and continue to move it forward. - spawn: Spawn>>, - - // Our two callbacks connected to the `Promise` that we returned to - // JavaScript. We'll be invoking one of these at the end. - resolve: Function, - reject: Function, - - // Shared state used to communicate waking up this future, this is the - // `Send + Sync` piece needed by the async task system. - waker: Arc, - } - - struct Waker { - value: AtomicI32, - }; - - impl Notify for Waker { - fn notify(&self, _id: usize) { - // Attempt to notify us by storing 1. If we're already 1 then we - // were previously notified and there's nothing to do. Otherwise - // we execute the native `notify` instruction to wake up the - // corresponding `waitAsync` that was waiting for the transition - // from 0 to 1. - let prev = self.value.swap(1, Ordering::SeqCst); - if prev == 1 { - return; - } - debug_assert_eq!(prev, 0); - unsafe { - core::arch::wasm32::atomic_notify( - &self.value as *const AtomicI32 as *mut i32, - 1, // number of threads to notify - ); - } - } - } - - impl Package { - fn poll(mut self) { - // Poll in a loop waiting for the future to become ready. Note that - // we probably shouldn't maximize synchronous work here but rather - // we should occasionally yield back to the runtime and schedule - // ourselves to resume this future later on. - // - // Note that 0 here means "need a notification" and 1 means "we got - // a notification". That means we're storing 0 into the `notified` - // slot and we're trying to read 1 to keep on going. - while self.waker.value.swap(0, Ordering::SeqCst) == 1 { - let (val, f) = match self.spawn.poll_future_notify(&self.waker, 0) { - // If the future is ready, immediately call the - // resolve/reject callback and then return as we're done. - Ok(Async::Ready(value)) => (value, &self.resolve), - Err(value) => (value, &self.reject), - - // ... otherwise let's break out and wait - Ok(Async::NotReady) => break, - }; - - // Call the resolution function, and then when we're done - // destroy ourselves through `drop` since our future is no - // longer needed. - drop(f.call1(&JsValue::undefined(), &val)); - return; - } - - // Create a `js_sys::Promise` using `Atomics.waitAsync` (or our - // polyfill) and then register its completion callback as simply - // calling this function again. - let promise = wait_async(&self.waker.value, 0).unchecked_into::(); - let closure = Closure::once_into_js(move || { - self.poll(); - }); - promise.then(&closure); - } - } -} - -fn wait_async(ptr: &AtomicI32, val: i32) -> js_sys::Promise { - // If `Atomics.waitAsync` isn't defined (as it isn't defined anywhere today) - // then we use our fallback, otherwise we use the native function. - if Atomics::get_wait_async().is_undefined() { - crate::wait_async_polyfill::wait_async(ptr, val) - } else { - let mem = wasm_bindgen::memory().unchecked_into::(); - Atomics::wait_async(&mem.buffer(), ptr as *const AtomicI32 as i32 / 4, val) - } -} diff --git a/crates/futures/src/lib.rs b/crates/futures/src/lib.rs index 57da7b365d0..bcb15c60072 100644 --- a/crates/futures/src/lib.rs +++ b/crates/futures/src/lib.rs @@ -31,104 +31,22 @@ //! running tasks on the next tick of the micro task queue. The futures built on //! top of it can be scheduled for execution by conversion into a JavaScript //! `Promise`. -//! -//! ```rust,no_run -//! extern crate futures; -//! extern crate js_sys; -//! extern crate wasm_bindgen; -//! extern crate wasm_bindgen_futures; -//! -//! use futures::{Async, Future, Poll}; -//! use wasm_bindgen::prelude::*; -//! use wasm_bindgen_futures::{JsFuture, future_to_promise}; -//! -//! /// A future that becomes ready after a tick of the micro task queue. -//! pub struct NextTick { -//! inner: JsFuture, -//! } -//! -//! impl NextTick { -//! /// Construct a new `NextTick` future. -//! pub fn new() -> NextTick { -//! // Create a resolved promise that will run its callbacks on the next -//! // tick of the micro task queue. -//! let promise = js_sys::Promise::resolve(&JsValue::NULL); -//! // Convert the promise into a `JsFuture`. -//! let inner = JsFuture::from(promise); -//! NextTick { inner } -//! } -//! } -//! -//! impl Future for NextTick { -//! type Item = (); -//! type Error = (); -//! -//! fn poll(&mut self) -> Poll<(), ()> { -//! // Polling a `NextTick` just forwards to polling if the inner promise is -//! // ready. -//! match self.inner.poll() { -//! Ok(Async::Ready(_)) => Ok(Async::Ready(())), -//! Ok(Async::NotReady) => Ok(Async::NotReady), -//! Err(_) => unreachable!( -//! "We only create NextTick with a resolved inner promise, never \ -//! a rejected one, so we can't get an error here" -//! ), -//! } -//! } -//! } -//! -//! /// Export a function to JavaScript that does some work in the next tick of the -//! /// micro task queue! -//! #[wasm_bindgen] -//! pub fn schedule_some_work_for_next_tick() -> js_sys::Promise { -//! let future = NextTick::new() -//! // Do some work... -//! .and_then(|_| { -//! Ok(42) -//! }) -//! // And then convert the `Item` and `Error` into `JsValue`. -//! .map(|result| { -//! JsValue::from(result) -//! }) -//! .map_err(|error| { -//! let js_error = js_sys::Error::new(&format!("uh oh! {:?}", error)); -//! JsValue::from(js_error) -//! }); -//! -//! // Convert the `Future` into a JavaScript -//! // `Promise`! -//! future_to_promise(future) -//! } -//! ``` #![cfg_attr(target_feature = "atomics", feature(stdsimd))] #![deny(missing_docs)] use cfg_if::cfg_if; -mod legacy_shared; -pub use legacy_shared::*; +mod shared; +pub use shared::*; cfg_if! { if #[cfg(target_feature = "atomics")] { - /// Contains a thread-safe version of this crate, with Futures 0.1 - mod legacy_atomics; - pub use legacy_atomics::*; - - /// Polyfill for `Atomics.waitAsync` function mod wait_async_polyfill; + mod multithread; + pub use multithread::*; } else { - mod legacy; - pub use legacy::*; - } -} - -#[cfg(feature = "futures_0_3")] -cfg_if! { - if #[cfg(target_feature = "atomics")] { - compile_error!("futures 0.3 support is not implemented with atomics yet"); - } else { - /// Contains a Futures 0.3 implementation of this crate. - pub mod futures_0_3; + mod singlethread; + pub use singlethread::*; } } diff --git a/crates/futures/src/multithread.rs b/crates/futures/src/multithread.rs new file mode 100644 index 00000000000..79afcc71d23 --- /dev/null +++ b/crates/futures/src/multithread.rs @@ -0,0 +1,184 @@ +use js_sys::Promise; +use std::future::Future; +use std::mem::ManuallyDrop; +use std::pin::Pin; +use std::sync::atomic::{AtomicI32, Ordering::SeqCst}; +use std::sync::Arc; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +// Duplicate a bit here because `then` takes a `JsValue` instead of a `Closure`. +#[wasm_bindgen] +extern "C" { + type MyPromise; + #[wasm_bindgen(method)] + fn then(this: &MyPromise, cb: &JsValue); + + type Atomics; + #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync)] + fn wait_async(buf: &JsValue, index: i32, value: i32) -> js_sys::Promise; + #[wasm_bindgen(static_method_of = Atomics, js_name = waitAsync, getter)] + fn get_wait_async() -> JsValue; +} + +pub use crate::shared::{spawn_local, JsFuture}; + +/// Converts a Rust `Future` into a JavaScript `Promise`. +/// +/// This function will take any future in Rust and schedule it to be executed, +/// returning a JavaScript `Promise` which can then be passed back to JavaScript +/// to get plumbed into the rest of a system. +/// +/// The `future` provided must adhere to `'static` because it'll be scheduled +/// to run in the background and cannot contain any stack references. The +/// returned `Promise` will be resolved or rejected when the future completes, +/// depending on whether it finishes with `Ok` or `Err`. +/// +/// # Panics +/// +/// Note that in wasm panics are currently translated to aborts, but "abort" in +/// this case means that a JavaScript exception is thrown. The wasm module is +/// still usable (likely erroneously) after Rust panics. +/// +/// If the `future` provided panics then the returned `Promise` **will not +/// resolve**. Instead it will be a leaked promise. This is an unfortunate +/// limitation of wasm currently that's hoped to be fixed one day! +pub fn future_to_promise(future: F) -> Promise +where + F: Future> + 'static, +{ + _future_to_promise(Box::new(future)) +} + +fn _future_to_promise(future: Box>>) -> Promise { + let mut future = Some(future); + js_sys::Promise::new(&mut move |resolve, reject| { + let future = match future.take() { + Some(f) => f, + None => wasm_bindgen::throw_str("cannot invoke twice"), + }; + let state = Arc::new(State { + value: AtomicI32::new(1), + }); + Package { + // Note that the unsafety should be ok here since we're always + // passing in valid pointers and we're handling cleanup with + // `Waker`. + waker: unsafe { Waker::from_raw(State::into_raw_waker(state.clone())) }, + state, + future: Pin::from(future), + resolve, + reject, + } + .poll(); + }) +} + +struct Package { + state: Arc, + future: Pin>>>, + resolve: js_sys::Function, + reject: js_sys::Function, + waker: Waker, +} + +struct State { + value: AtomicI32, +} + +impl Package { + fn poll(mut self) { + // Flag ourselves as ready to receive another notification. We should + // never enter this method unless our `value` is set to `1`, so assert + // that as well. + let prev = self.state.value.swap(0, SeqCst); + debug_assert_eq!(prev, 1); + + // Afterwards start making progress on the future by calling the `poll` + // function. If we get `Ready` then we simply invoke the appropriate JS + // function to resolve the JS `Promise` we're connected to. + // + // If `Pending` is received then we're guaranteed to eventually receive + // an `atomic.notify` as well as a store to `1` in `self.state.value`. + // In this case we create a new promise (using `Atomics.waitAsync`) and + // then we register that promise to continue polling when it's resolved. + // Note that if a `wake` happened while we were polling or after we see + // `Pending` then the promise should end up resolving immediately due to + // the atomicity of `Atomics.waitAsync` with `Atomics.notify`. + let mut cx = Context::from_waker(&self.waker); + let (f, val) = match self.future.as_mut().poll(&mut cx) { + Poll::Ready(Ok(val)) => (&self.resolve, val), + Poll::Ready(Err(val)) => (&self.reject, val), + + // Create a `js_sys::Promise` using `Atomics.waitAsync` (or our + // polyfill) and then register its completion callback as simply + // calling this function again. + Poll::Pending => { + let promise = wait_async(&self.state.value, 0).unchecked_into::(); + let closure = Closure::once_into_js(move || { + self.poll(); + }); + promise.then(&closure); + return; + } + }; + f.call1(&JsValue::undefined(), &val).unwrap_throw(); + } +} + +impl State { + fn wake(&self) { + // Attempt to notify us by storing 1. If we're already 1 then we were + // previously notified and there's nothing to do. Otherwise we execute + // the native `notify` instruction to wake up the corresponding + // `waitAsync` that was waiting for the transition from 0 to 1. + let prev = self.value.swap(1, SeqCst); + if prev == 1 { + return; + } + debug_assert_eq!(prev, 0); + unsafe { + core::arch::wasm32::atomic_notify( + &self.value as *const AtomicI32 as *mut i32, + 1, // number of threads to notify + ); + } + } + + unsafe fn into_raw_waker(me: Arc) -> RawWaker { + const VTABLE: RawWakerVTable = + RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop); + RawWaker::new(Arc::into_raw(me) as *const (), &VTABLE) + } +} + +unsafe fn raw_clone(ptr: *const ()) -> RawWaker { + let ptr = ManuallyDrop::new(Arc::from_raw(ptr as *const State)); + State::into_raw_waker((*ptr).clone()) +} + +unsafe fn raw_wake(ptr: *const ()) { + let ptr = Arc::from_raw(ptr as *const State); + ptr.wake(); +} + +unsafe fn raw_wake_by_ref(ptr: *const ()) { + let ptr = ManuallyDrop::new(Arc::from_raw(ptr as *const State)); + ptr.wake(); +} + +unsafe fn raw_drop(ptr: *const ()) { + drop(Arc::from_raw(ptr as *const State)); +} + +fn wait_async(ptr: &AtomicI32, val: i32) -> js_sys::Promise { + // If `Atomics.waitAsync` isn't defined (as it isn't defined anywhere today) + // then we use our fallback, otherwise we use the native function. + if Atomics::get_wait_async().is_undefined() { + crate::wait_async_polyfill::wait_async(ptr, val) + } else { + let mem = wasm_bindgen::memory().unchecked_into::(); + Atomics::wait_async(&mem.buffer(), ptr as *const AtomicI32 as i32 / 4, val) + } +} diff --git a/crates/futures/src/legacy_shared.rs b/crates/futures/src/shared.rs similarity index 54% rename from crates/futures/src/legacy_shared.rs rename to crates/futures/src/shared.rs index 8b9434555cf..71fab9d3e65 100644 --- a/crates/futures/src/legacy_shared.rs +++ b/crates/futures/src/shared.rs @@ -1,10 +1,10 @@ -use futures::future; -use futures::prelude::*; -use futures::sync::oneshot; use js_sys::Promise; use std::cell::RefCell; use std::fmt; +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; +use std::task::{Context, Poll, Waker}; use wasm_bindgen::prelude::*; /// A Rust `Future` backed by a JavaScript `Promise`. @@ -16,7 +16,13 @@ use wasm_bindgen::prelude::*; /// /// Currently this type is constructed with `JsFuture::from`. pub struct JsFuture { - rx: oneshot::Receiver>, + inner: Rc>, +} + +struct Inner { + result: Option>, + task: Option, + callbacks: Option<(Closure, Closure)>, } impl fmt::Debug for JsFuture { @@ -42,49 +48,54 @@ impl From for JsFuture { // have to be self-contained. Through the `Closure::once` and some // `Rc`-trickery we can arrange for both instances of `Closure`, and the // `Rc`, to all be destroyed once the first one is called. - let (tx, rx) = oneshot::channel(); - let state = Rc::new(RefCell::new(None)); + let state = Rc::new(RefCell::new(Inner { + result: None, + task: None, + callbacks: None, + })); let state2 = state.clone(); let resolve = Closure::once(move |val| finish(&state2, Ok(val))); let state2 = state.clone(); let reject = Closure::once(move |val| finish(&state2, Err(val))); js.then2(&resolve, &reject); - *state.borrow_mut() = Some((tx, resolve, reject)); + state.borrow_mut().callbacks = Some((resolve, reject)); - return JsFuture { rx }; + return JsFuture { inner: state }; - fn finish( - state: &RefCell< - Option<( - oneshot::Sender>, - Closure, - Closure, - )>, - >, - val: Result, - ) { - match state.borrow_mut().take() { - // We don't have any guarantee that anyone's still listening at this - // point (the Rust `JsFuture` could have been dropped) so simply - // ignore any errors here. - Some((tx, _, _)) => drop(tx.send(val)), - None => wasm_bindgen::throw_str("cannot finish twice"), + fn finish(state: &RefCell, val: Result) { + // First up drop our closures as they'll never be invoked again and + // this is our chance to clean up their state. Next store the value + // into the internal state, and then finally if any task was waiting + // on the value wake it up and let them know it's there. + let task = { + let mut state = state.borrow_mut(); + debug_assert!(state.callbacks.is_some()); + debug_assert!(state.result.is_none()); + drop(state.callbacks.take()); + state.result = Some(val); + state.task.take() + }; + if let Some(task) = task { + task.wake() } } } } impl Future for JsFuture { - type Item = JsValue; - type Error = JsValue; + type Output = Result; - fn poll(&mut self) -> Poll { - match self.rx.poll() { - Ok(Async::Ready(val)) => val.map(Async::Ready), - Ok(Async::NotReady) => Ok(Async::NotReady), - Err(_) => wasm_bindgen::throw_str("cannot cancel"), + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let mut inner = self.inner.borrow_mut(); + // If our value has come in then we return it... + if let Some(val) = inner.result.take() { + return Poll::Ready(val) } + // ... otherwise we arrange ourselves to get woken up once the value + // does come in + inner.task = Some(cx.waker().clone()); + Poll::Pending } } @@ -98,11 +109,10 @@ impl Future for JsFuture { /// This function has the same panic behavior as `future_to_promise`. pub fn spawn_local(future: F) where - F: Future + 'static, + F: Future + 'static, { - crate::future_to_promise( - future - .map(|()| JsValue::undefined()) - .or_else(|()| future::ok::(JsValue::undefined())), - ); + crate::future_to_promise(async { + future.await; + Ok(JsValue::undefined()) + }); } diff --git a/crates/futures/src/singlethread.rs b/crates/futures/src/singlethread.rs new file mode 100644 index 00000000000..dae2cfa438a --- /dev/null +++ b/crates/futures/src/singlethread.rs @@ -0,0 +1,142 @@ +pub use crate::shared::{spawn_local, JsFuture}; +use js_sys::Promise; +use std::cell::{Cell, RefCell}; +use std::future::Future; +use std::mem::ManuallyDrop; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; + +/// Converts a Rust `Future` into a JavaScript `Promise`. +/// +/// This function will take any future in Rust and schedule it to be executed, +/// returning a JavaScript `Promise` which can then be passed back to JavaScript +/// to get plumbed into the rest of a system. +/// +/// The `future` provided must adhere to `'static` because it'll be scheduled +/// to run in the background and cannot contain any stack references. The +/// returned `Promise` will be resolved or rejected when the future completes, +/// depending on whether it finishes with `Ok` or `Err`. +/// +/// # Panics +/// +/// Note that in wasm panics are currently translated to aborts, but "abort" in +/// this case means that a JavaScript exception is thrown. The wasm module is +/// still usable (likely erroneously) after Rust panics. +/// +/// If the `future` provided panics then the returned `Promise` **will not +/// resolve**. Instead it will be a leaked promise. This is an unfortunate +/// limitation of wasm currently that's hoped to be fixed one day! +pub fn future_to_promise(future: F) -> Promise +where + F: Future> + 'static, +{ + _future_to_promise(Box::new(future)) +} + +fn _future_to_promise(future: Box>>) -> Promise { + let mut future = Some(future); + js_sys::Promise::new(&mut move |resolve, reject| { + let future = match future.take() { + Some(f) => f, + None => wasm_bindgen::throw_str("cannot invoke twice"), + }; + let state = Rc::new(State { + queued: Cell::new(true), + future: RefCell::new(Some(Pin::from(future))), + resolve, + reject, + }); + State::poll(&state); + }) +} + +struct State { + queued: Cell, + future: RefCell>>>>>, + resolve: js_sys::Function, + reject: js_sys::Function, +} + +impl State { + fn poll(me: &Rc) { + debug_assert!(me.queued.get()); + me.queued.set(false); + let waker = unsafe { Waker::from_raw(State::into_raw_waker(me.clone())) }; + let mut future = me.future.borrow_mut(); + + let mut done = false; + if let Some(future) = &mut *future { + match Future::poll(future.as_mut(), &mut Context::from_waker(&waker)) { + Poll::Ready(Ok(val)) => { + me.resolve.call1(&JsValue::undefined(), &val).unwrap_throw(); + done = true; + } + Poll::Ready(Err(val)) => { + me.reject.call1(&JsValue::undefined(), &val).unwrap_throw(); + done = true; + } + Poll::Pending => {} + } + } + if done { + debug_assert!(future.is_some()); + drop(future.take()); + return; + } + } + + fn wake(me: &Rc) { + // If we're already enqueued on the microtask queue there's nothing else + // to do, this is a duplicate notification. + if me.queued.replace(true) { + return; + } + + // Use `Promise.then` on a resolved promise to place our execution + // onto the next turn of the microtask queue, enqueueing our poll + // operation. If we were to poll immediately we run the risk of blowing + // the stack. + let promise = Promise::resolve(&JsValue::undefined()); + let promise = promise.unchecked_ref::(); + let me = me.clone(); + let closure = Closure::once_into_js(move || { + State::poll(&me); + }); + promise.then(&closure); + + #[wasm_bindgen] + extern "C" { + type MyPromise; + #[wasm_bindgen(js_class = Promise, method)] + fn then(this: &MyPromise, closure: &JsValue); + } + } + + unsafe fn into_raw_waker(me: Rc) -> RawWaker { + const VTABLE: RawWakerVTable = + RawWakerVTable::new(raw_clone, raw_wake, raw_wake_by_ref, raw_drop); + RawWaker::new(Rc::into_raw(me) as *const (), &VTABLE) + } +} + +unsafe fn raw_clone(ptr: *const ()) -> RawWaker { + let ptr = ManuallyDrop::new(Rc::from_raw(ptr as *const State)); + State::into_raw_waker((*ptr).clone()) +} + +unsafe fn raw_wake(ptr: *const ()) { + let ptr = Rc::from_raw(ptr as *const State); + State::wake(&ptr); +} + +unsafe fn raw_wake_by_ref(ptr: *const ()) { + let ptr = ManuallyDrop::new(Rc::from_raw(ptr as *const State)); + State::wake(&ptr); +} + +unsafe fn raw_drop(ptr: *const ()) { + drop(Rc::from_raw(ptr as *const State)); +} diff --git a/crates/js-sys/Cargo.toml b/crates/js-sys/Cargo.toml index becb21069ec..a8735e42632 100644 --- a/crates/js-sys/Cargo.toml +++ b/crates/js-sys/Cargo.toml @@ -22,6 +22,5 @@ doctest = false wasm-bindgen = { path = "../..", version = "0.2.50" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -futures = "0.1.20" -wasm-bindgen-test = { path = '../test', version = '=0.2.50' } -wasm-bindgen-futures = { path = '../futures', version = '=0.3.27' } +wasm-bindgen-test = { path = '../test', version = '=0.3.0' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.0' } diff --git a/crates/js-sys/tests/wasm/WebAssembly.rs b/crates/js-sys/tests/wasm/WebAssembly.rs index a662f9f0e2d..7a06e256a2c 100644 --- a/crates/js-sys/tests/wasm/WebAssembly.rs +++ b/crates/js-sys/tests/wasm/WebAssembly.rs @@ -1,4 +1,3 @@ -use futures::Future; use js_sys::*; use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen_futures::JsFuture; @@ -38,32 +37,25 @@ fn validate() { assert!(WebAssembly::validate(&get_bad_type_wasm()).is_err()); } -#[wasm_bindgen_test(async)] -fn compile_compile_error() -> impl Future { +#[wasm_bindgen_test] +async fn compile_compile_error() { let p = WebAssembly::compile(&get_invalid_wasm()); - JsFuture::from(p).map(|_| unreachable!()).or_else(|e| { - assert!(e.is_instance_of::()); - Ok(()) - }) + let e = JsFuture::from(p).await.unwrap_err(); + assert!(e.is_instance_of::()); } -#[wasm_bindgen_test(async)] -fn compile_type_error() -> impl Future { +#[wasm_bindgen_test] +async fn compile_type_error() { let p = WebAssembly::compile(&get_bad_type_wasm()); - JsFuture::from(p).map(|_| unreachable!()).or_else(|e| { - assert!(e.is_instance_of::()); - Ok(()) - }) + let e = JsFuture::from(p).await.unwrap_err(); + assert!(e.is_instance_of::()); } -#[wasm_bindgen_test(async)] -fn compile_valid() -> impl Future { +#[wasm_bindgen_test] +async fn compile_valid() { let p = WebAssembly::compile(&get_valid_wasm()); - JsFuture::from(p) - .map(|module| { - assert!(module.is_instance_of::()); - }) - .map_err(|_| unreachable!()) + let module = JsFuture::from(p).await.unwrap(); + assert!(module.is_instance_of::()); } #[wasm_bindgen_test] @@ -182,26 +174,24 @@ fn webassembly_instance() { assert!(Reflect::has(exports.as_ref(), &"exported_func".into()).unwrap()); } -#[wasm_bindgen_test(async)] -fn instantiate_module() -> impl Future { +#[wasm_bindgen_test] +async fn instantiate_module() { let module = WebAssembly::Module::new(&get_valid_wasm()).unwrap(); let imports = get_imports(); let p = WebAssembly::instantiate_module(&module, &imports); - JsFuture::from(p).map(|inst| { - assert!(inst.is_instance_of::()); - }) + let inst = JsFuture::from(p).await.unwrap(); + assert!(inst.is_instance_of::()); } -#[wasm_bindgen_test(async)] -fn instantiate_streaming() -> impl Future { +#[wasm_bindgen_test] +async fn instantiate_streaming() { let response = Promise::resolve(&get_valid_wasm()); let imports = get_imports(); let p = WebAssembly::instantiate_streaming(&response, &imports); - JsFuture::from(p).map(|obj| { - assert!(Reflect::get(obj.as_ref(), &"instance".into()) - .unwrap() - .is_instance_of::()); - }) + let obj = JsFuture::from(p).await.unwrap(); + assert!(Reflect::get(obj.as_ref(), &"instance".into()) + .unwrap() + .is_instance_of::()); } #[wasm_bindgen_test] diff --git a/crates/js-sys/tests/wasm/main.rs b/crates/js-sys/tests/wasm/main.rs index abc86acd576..17f80bdfc99 100755 --- a/crates/js-sys/tests/wasm/main.rs +++ b/crates/js-sys/tests/wasm/main.rs @@ -1,12 +1,6 @@ #![cfg(target_arch = "wasm32")] #![allow(non_snake_case)] -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; -extern crate wasm_bindgen_test; - pub mod Array; pub mod ArrayBuffer; pub mod ArrayIterator; diff --git a/crates/test-macro/Cargo.toml b/crates/test-macro/Cargo.toml index 64071e4640b..e3e6f01d014 100644 --- a/crates/test-macro/Cargo.toml +++ b/crates/test-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test-macro" -version = "0.2.50" +version = "0.3.0" authors = ["The wasm-bindgen Developers"] description = "Internal testing macro for wasm-bindgen" license = "MIT/Apache-2.0" @@ -8,8 +8,8 @@ repository = "https://github.com/rustwasm/wasm-bindgen" edition = "2018" [dependencies] -proc-macro2 = "0.4" -quote = "0.6" +proc-macro2 = "1.0" +quote = "1.0" [lib] proc-macro = true diff --git a/crates/test-macro/src/lib.rs b/crates/test-macro/src/lib.rs index 65ac76cb181..ce7c5851655 100644 --- a/crates/test-macro/src/lib.rs +++ b/crates/test-macro/src/lib.rs @@ -35,6 +35,9 @@ pub fn wasm_bindgen_test( while let Some(token) = body.next() { leading_tokens.push(token.clone()); if let TokenTree::Ident(token) = token { + if token == "async" { + r#async = true; + } if token == "fn" { break; } diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 700d61b3002..f8d560088fd 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-bindgen-test" -version = "0.2.50" +version = "0.3.0" authors = ["The wasm-bindgen Developers"] description = "Internal testing crate for wasm-bindgen" license = "MIT/Apache-2.0" @@ -9,12 +9,11 @@ edition = "2018" [dependencies] console_error_panic_hook = '0.1' -futures = "0.1" js-sys = { path = '../js-sys', version = '0.3.27' } scoped-tls = "1.0" wasm-bindgen = { path = '../..', version = '0.2.50' } -wasm-bindgen-futures = { path = '../futures', version = '0.3.27' } -wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.50' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.0' } +wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.0' } [lib] test = false diff --git a/crates/test/sample/Cargo.toml b/crates/test/sample/Cargo.toml index de4f2756026..05544ab4a3d 100644 --- a/crates/test/sample/Cargo.toml +++ b/crates/test/sample/Cargo.toml @@ -2,12 +2,12 @@ name = "sample" version = "0.1.0" authors = ["The wasm-bindgen Developers"] +edition = "2018" [lib] test = false [dependencies] -futures = "0.1" js-sys = { path = '../../js-sys' } wasm-bindgen = { path = '../../..' } wasm-bindgen-futures = { path = '../../futures' } diff --git a/crates/test/sample/src/lib.rs b/crates/test/sample/src/lib.rs index 23852f99a94..5c878ccf63c 100644 --- a/crates/test/sample/src/lib.rs +++ b/crates/test/sample/src/lib.rs @@ -1,28 +1,23 @@ -#[macro_use] -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; - -use std::time::Duration; - -use futures::prelude::*; use js_sys::Promise; +use std::task::{Poll, Context}; +use std::pin::Pin; +use std::time::Duration; use wasm_bindgen::prelude::*; +use std::future::Future; use wasm_bindgen_futures::JsFuture; pub struct Timeout { - id: u32, + id: JsValue, inner: JsFuture, } #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = setTimeout)] - fn set_timeout(closure: JsValue, millis: f64) -> u32; + fn set_timeout(closure: JsValue, millis: f64) -> JsValue; #[wasm_bindgen(js_name = clearTimeout)] - fn clear_timeout(id: u32); + fn clear_timeout(id: &JsValue); } impl Timeout { @@ -47,17 +42,15 @@ impl Timeout { } impl Future for Timeout { - type Item = (); - type Error = JsValue; + type Output = (); - fn poll(&mut self) -> Poll<(), JsValue> { - let _obj = try_ready!(self.inner.poll()); - Ok(().into()) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + Pin::new(&mut self.inner).poll(cx).map(|_| ()) } } impl Drop for Timeout { fn drop(&mut self) { - clear_timeout(self.id); + clear_timeout(&self.id); } } diff --git a/crates/test/sample/tests/browser.rs b/crates/test/sample/tests/browser.rs index 0f2bcd4c807..39c142bcf07 100644 --- a/crates/test/sample/tests/browser.rs +++ b/crates/test/sample/tests/browser.rs @@ -1,8 +1,3 @@ -extern crate futures; -extern crate sample; -extern crate wasm_bindgen; -extern crate wasm_bindgen_test; - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); pub mod common; diff --git a/crates/test/sample/tests/common/mod.rs b/crates/test/sample/tests/common/mod.rs index cde7d6deb7f..82860db4d50 100644 --- a/crates/test/sample/tests/common/mod.rs +++ b/crates/test/sample/tests/common/mod.rs @@ -1,8 +1,5 @@ use std::time::Duration; - -use futures::prelude::*; use sample::Timeout; -use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; #[wasm_bindgen_test] @@ -10,15 +7,13 @@ fn pass() { console_log!("DO NOT SEE ME"); } -#[wasm_bindgen_test(async)] -fn pass_after_2s() -> impl Future { +#[wasm_bindgen_test] +async fn pass_after_2s() { console_log!("immediate log"); - Timeout::new(Duration::new(1, 0)).and_then(|()| { - console_log!("log after 1s"); - Timeout::new(Duration::new(1, 0)).map(|()| { - console_log!("log at end"); - }) - }) + Timeout::new(Duration::new(1, 0)).await; + console_log!("log after 1s"); + Timeout::new(Duration::new(1, 0)).await; + console_log!("log at end"); } #[wasm_bindgen_test] @@ -27,16 +22,13 @@ fn fail() { panic!("this is a failing test"); } -#[wasm_bindgen_test(async)] -fn fail_after_3s() -> impl Future { +#[wasm_bindgen_test] +async fn fail_after_3s() { console_log!("immediate log"); - Timeout::new(Duration::new(1, 0)).and_then(|()| { - console_log!("log after 1s"); - Timeout::new(Duration::new(1, 0)).and_then(|()| { - console_log!("log after 2s"); - Timeout::new(Duration::new(1, 0)).map(|()| { - panic!("end"); - }) - }) - }) + Timeout::new(Duration::new(1, 0)).await; + console_log!("log after 1s"); + Timeout::new(Duration::new(1, 0)).await; + console_log!("log after 2s"); + Timeout::new(Duration::new(1, 0)).await; + panic!("end"); } diff --git a/crates/test/sample/tests/node.rs b/crates/test/sample/tests/node.rs index e9cb596c3fa..34994bf5afc 100644 --- a/crates/test/sample/tests/node.rs +++ b/crates/test/sample/tests/node.rs @@ -1,6 +1 @@ -extern crate futures; -extern crate sample; -extern crate wasm_bindgen; -extern crate wasm_bindgen_test; - pub mod common; diff --git a/crates/test/src/rt/mod.rs b/crates/test/src/rt/mod.rs index 597c95c8734..bf08fe19e42 100644 --- a/crates/test/src/rt/mod.rs +++ b/crates/test/src/rt/mod.rs @@ -87,14 +87,14 @@ // Overall this is all somewhat in flux as it's pretty new, and feedback is // always of course welcome! +use console_error_panic_hook; +use js_sys::{Array, Function, Promise}; use std::cell::{Cell, RefCell}; use std::fmt; +use std::future::Future; +use std::pin::Pin; use std::rc::Rc; - -use console_error_panic_hook; -use futures::future; -use futures::prelude::*; -use js_sys::{Array, Function, Promise}; +use std::task::{self, Poll}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; @@ -157,7 +157,7 @@ struct State { /// future is polled. struct Test { name: String, - future: Box>, + future: Pin>>>, output: Rc>, } @@ -286,10 +286,11 @@ impl Context { // Now that we've collected all our tests we wrap everything up in a // future to actually do all the processing, and pass it out to JS as a // `Promise`. - let future = ExecuteTests(self.state.clone()) - .map(JsValue::from) - .map_err(|e| match e {}); - future_to_promise(future) + let state = self.state.clone(); + future_to_promise(async { + let passed = ExecuteTests(state).await; + Ok(JsValue::from(passed)) + }) } } @@ -358,7 +359,7 @@ impl Context { /// Entry point for a synchronous test in wasm. The `#[wasm_bindgen_test]` /// macro generates invocations of this method. pub fn execute_sync(&self, name: &str, f: impl FnOnce() + 'static) { - self.execute(name, future::lazy(|| Ok(f()))); + self.execute(name, async { f() }); } /// Entry point for an asynchronous in wasm. The @@ -366,12 +367,12 @@ impl Context { /// method. pub fn execute_async(&self, name: &str, f: impl FnOnce() -> F + 'static) where - F: Future + 'static, + F: Future + 'static, { - self.execute(name, future::lazy(f)) + self.execute(name, async { f().await }) } - fn execute(&self, name: &str, test: impl Future + 'static) { + fn execute(&self, name: &str, test: impl Future + 'static) { // If our test is filtered out, record that it was filtered and move // on, nothing to do here. let filter = self.state.filter.borrow(); @@ -392,7 +393,7 @@ impl Context { }; self.state.remaining.borrow_mut().push(Test { name: name.to_string(), - future: Box::new(future), + future: Pin::from(Box::new(future)), output, }); } @@ -400,23 +401,19 @@ impl Context { struct ExecuteTests(Rc); -enum Never {} - impl Future for ExecuteTests { - type Item = bool; - type Error = Never; + type Output = bool; - fn poll(&mut self) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { let mut running = self.0.running.borrow_mut(); let mut remaining = self.0.remaining.borrow_mut(); // First up, try to make progress on all active tests. Remove any // finished tests. for i in (0..running.len()).rev() { - let result = match running[i].future.poll() { - Ok(Async::Ready(_jsavl)) => Ok(()), - Ok(Async::NotReady) => continue, - Err(e) => Err(e), + let result = match running[i].future.as_mut().poll(cx) { + Poll::Ready(result) => result, + Poll::Pending => continue, }; let test = running.remove(i); self.0.log_test_result(test, result); @@ -431,13 +428,12 @@ impl Future for ExecuteTests { Some(test) => test, None => break, }; - let result = match test.future.poll() { - Ok(Async::Ready(())) => Ok(()), - Ok(Async::NotReady) => { + let result = match test.future.as_mut().poll(cx) { + Poll::Ready(result) => result, + Poll::Pending => { running.push(test); continue; } - Err(e) => Err(e), }; self.0.log_test_result(test, result); } @@ -445,7 +441,7 @@ impl Future for ExecuteTests { // Tests are still executing, we're registered to get a notification, // keep going. if running.len() != 0 { - return Ok(Async::NotReady); + return Poll::Pending; } // If there are no tests running then we must have finished everything, @@ -454,7 +450,7 @@ impl Future for ExecuteTests { self.0.print_results(); let all_passed = self.0.failures.borrow().len() == 0; - Ok(Async::Ready(all_passed)) + Poll::Ready(all_passed) } } @@ -561,17 +557,28 @@ extern "C" { fn __wbg_test_invoke(f: &mut dyn FnMut()) -> Result<(), JsValue>; } -impl> Future for TestFuture { - type Item = F::Item; - type Error = F::Error; +impl Future for TestFuture { + type Output = Result; - fn poll(&mut self) -> Poll { - let test = &mut self.test; + fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { + let output = self.output.clone(); + // Use `new_unchecked` here to project our own pin, and we never + // move `test` so this should be safe + let test = unsafe { Pin::map_unchecked_mut(self, |me| &mut me.test) }; let mut future_output = None; - CURRENT_OUTPUT.set(&self.output, || { - __wbg_test_invoke(&mut || future_output = Some(test.poll())) - })?; - future_output.unwrap() + let result = CURRENT_OUTPUT.set(&output, || { + let mut test = Some(test); + __wbg_test_invoke(&mut || { + let test = test.take().unwrap_throw(); + future_output = Some(test.poll(cx)) + }) + }); + match (result, future_output) { + (_, Some(Poll::Ready(e))) => Poll::Ready(Ok(e)), + (_, Some(Poll::Pending)) => Poll::Pending, + (Err(e), _) => Poll::Ready(Err(e)), + (Ok(_), None) => wasm_bindgen::throw_str("invalid poll state"), + } } } diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml index 122dde4c97e..1301d20abaa 100644 --- a/crates/web-sys/Cargo.toml +++ b/crates/web-sys/Cargo.toml @@ -30,9 +30,8 @@ wasm-bindgen = { path = "../..", version = "0.2.50" } js-sys = { path = '../js-sys', version = '0.3.27' } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -futures = "0.1" -wasm-bindgen-test = { path = '../test', version = '0.2.50' } -wasm-bindgen-futures = { path = '../futures', version = '0.3.27' } +wasm-bindgen-test = { path = '../test', version = '0.3.0' } +wasm-bindgen-futures = { path = '../futures', version = '0.4.0' } # This list is generated by passing `__WASM_BINDGEN_DUMP_FEATURES=foo` when # compiling this crate which dumps the total list of features to a file called diff --git a/crates/web-sys/tests/wasm/event.rs b/crates/web-sys/tests/wasm/event.rs index bd12cb0dda8..08d74ec83ad 100644 --- a/crates/web-sys/tests/wasm/event.rs +++ b/crates/web-sys/tests/wasm/event.rs @@ -1,4 +1,3 @@ -use futures::future::Future; use js_sys::{Object, Promise}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -11,22 +10,22 @@ extern "C" { fn new_event() -> Promise; } -#[wasm_bindgen_test(async)] -fn event() -> impl Future { - JsFuture::from(new_event()).map(Event::from).map(|event| { - // All DOM interfaces should inherit from `Object`. - assert!(event.is_instance_of::()); - let _: &Object = event.as_ref(); +#[wasm_bindgen_test] +async fn event() { + let result = JsFuture::from(new_event()).await.unwrap(); + let event = Event::from(result); + // All DOM interfaces should inherit from `Object`. + assert!(event.is_instance_of::()); + let _: &Object = event.as_ref(); - // These should match `new Event`. - assert!(event.bubbles()); - assert!(event.cancelable()); - assert!(event.composed()); + // These should match `new Event`. + assert!(event.bubbles()); + assert!(event.cancelable()); + assert!(event.composed()); - // The default behavior not initially prevented, but after - // we call `prevent_default` it better be. - assert!(!event.default_prevented()); - event.prevent_default(); - assert!(event.default_prevented()); - }) + // The default behavior not initially prevented, but after + // we call `prevent_default` it better be. + assert!(!event.default_prevented()); + event.prevent_default(); + assert!(event.default_prevented()); } diff --git a/crates/web-sys/tests/wasm/main.rs b/crates/web-sys/tests/wasm/main.rs index f27d6af71f5..3d0c125a381 100644 --- a/crates/web-sys/tests/wasm/main.rs +++ b/crates/web-sys/tests/wasm/main.rs @@ -1,12 +1,5 @@ #![cfg(target_arch = "wasm32")] -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen; -extern crate wasm_bindgen_futures; -extern crate wasm_bindgen_test; -extern crate web_sys; - use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); diff --git a/crates/web-sys/tests/wasm/response.rs b/crates/web-sys/tests/wasm/response.rs index f22495abd6b..045062df14f 100644 --- a/crates/web-sys/tests/wasm/response.rs +++ b/crates/web-sys/tests/wasm/response.rs @@ -1,8 +1,3 @@ -extern crate futures; -extern crate js_sys; -extern crate wasm_bindgen_futures; - -use futures::Future; use js_sys::{ArrayBuffer, DataView}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -23,20 +18,19 @@ fn test_response_from_js() { assert_eq!(response.status(), 501); } -#[wasm_bindgen_test(async)] -fn test_response_from_bytes() -> impl Future { +#[wasm_bindgen_test] +async fn test_response_from_bytes() { let mut bytes: [u8; 3] = [1, 3, 5]; let response = Response::new_with_opt_u8_array(Some(&mut bytes)).unwrap(); assert!(response.ok()); assert_eq!(response.status(), 200); let buf_promise = response.array_buffer().unwrap(); - JsFuture::from(buf_promise).map(move |buf_val| { - assert!(buf_val.is_instance_of::()); - let array_buf: ArrayBuffer = buf_val.dyn_into().unwrap(); - let data_view = DataView::new(&array_buf, 0, bytes.len()); - for (i, byte) in bytes.iter().enumerate() { - assert_eq!(&data_view.get_uint8(i), byte); - } - }) + let buf_val = JsFuture::from(buf_promise).await.unwrap(); + assert!(buf_val.is_instance_of::()); + let array_buf: ArrayBuffer = buf_val.dyn_into().unwrap(); + let data_view = DataView::new(&array_buf, 0, bytes.len()); + for (i, byte) in bytes.iter().enumerate() { + assert_eq!(&data_view.get_uint8(i), byte); + } } diff --git a/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs b/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs index 15dbe7aa11a..b21edcb6c59 100644 --- a/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs +++ b/crates/web-sys/tests/wasm/rtc_rtp_transceiver_direction.rs @@ -2,11 +2,6 @@ use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen_futures::JsFuture; use wasm_bindgen_test::*; -use futures::{ - future::{ok, IntoFuture}, - Future, -}; - use web_sys::{ RtcPeerConnection, RtcRtpTransceiver, RtcRtpTransceiverDirection, RtcRtpTransceiverInit, RtcSessionDescriptionInit, @@ -20,10 +15,10 @@ extern "C" { fn is_unified_avail() -> bool; } -#[wasm_bindgen_test(async)] -fn rtc_rtp_transceiver_direction() -> Box> { +#[wasm_bindgen_test] +async fn rtc_rtp_transceiver_direction() { if !is_unified_avail() { - return Box::new(Ok(()).into_future()); + return; } let mut tr_init: RtcRtpTransceiverInit = RtcRtpTransceiverInit::new(); @@ -39,54 +34,39 @@ fn rtc_rtp_transceiver_direction() -> Box let pc2: RtcPeerConnection = RtcPeerConnection::new().unwrap(); - let r = exchange_sdps(pc1, pc2).and_then(move |(_, p2)| { - assert_eq!(tr1.direction(), RtcRtpTransceiverDirection::Sendonly); - assert_eq!( - tr1.current_direction(), - Some(RtcRtpTransceiverDirection::Sendonly) - ); - - let tr2: RtcRtpTransceiver = js_sys::try_iter(&p2.get_transceivers()) - .unwrap() - .unwrap() - .next() - .unwrap() - .unwrap() - .unchecked_into(); - - assert_eq!(tr2.direction(), RtcRtpTransceiverDirection::Recvonly); - assert_eq!( - tr2.current_direction(), - Some(RtcRtpTransceiverDirection::Recvonly) - ); + let (_, p2) = exchange_sdps(pc1, pc2).await; + assert_eq!(tr1.direction(), RtcRtpTransceiverDirection::Sendonly); + assert_eq!( + tr1.current_direction(), + Some(RtcRtpTransceiverDirection::Sendonly) + ); - Ok(()) - }); + let tr2: RtcRtpTransceiver = js_sys::try_iter(&p2.get_transceivers()) + .unwrap() + .unwrap() + .next() + .unwrap() + .unwrap() + .unchecked_into(); - Box::new(r) + assert_eq!(tr2.direction(), RtcRtpTransceiverDirection::Recvonly); + assert_eq!( + tr2.current_direction(), + Some(RtcRtpTransceiverDirection::Recvonly) + ); } -fn exchange_sdps( +async fn exchange_sdps( p1: RtcPeerConnection, p2: RtcPeerConnection, -) -> impl Future { - JsFuture::from(p1.create_offer()) - .and_then(move |offer| { - let offer = offer.unchecked_into::(); - JsFuture::from(p1.set_local_description(&offer)).join4( - JsFuture::from(p2.set_remote_description(&offer)), - Ok(p1), - Ok(p2), - ) - }) - .and_then(|(_, _, p1, p2)| JsFuture::from(p2.create_answer()).join3(Ok(p1), Ok(p2))) - .and_then(|(answer, p1, p2)| { - let answer = answer.unchecked_into::(); - JsFuture::from(p2.set_local_description(&answer)).join4( - JsFuture::from(p1.set_remote_description(&answer)), - Ok(p1), - Ok(p2), - ) - }) - .and_then(|(_, _, p1, p2)| Ok((p1, p2))) +) -> (RtcPeerConnection, RtcPeerConnection) { + let offer = JsFuture::from(p1.create_offer()).await.unwrap(); + let offer = offer.unchecked_into::(); + JsFuture::from(p1.set_local_description(&offer)).await.unwrap(); + JsFuture::from(p2.set_remote_description(&offer)).await.unwrap(); + let answer = JsFuture::from(p2.create_answer()).await.unwrap(); + let answer = answer.unchecked_into::(); + JsFuture::from(p2.set_local_description(&answer)).await.unwrap(); + JsFuture::from(p1.set_remote_description(&answer)).await.unwrap(); + (p1, p2) } diff --git a/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs b/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs index e58987df3de..92c3161904d 100644 --- a/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs +++ b/crates/web-sys/tests/wasm/whitelisted_immutable_slices.rs @@ -11,7 +11,6 @@ //! @see https://github.com/rustwasm/wasm-bindgen/issues/1005 use wasm_bindgen::prelude::*; -use wasm_bindgen_test::*; use web_sys::{WebGl2RenderingContext, WebGlRenderingContext}; #[wasm_bindgen(module = "/tests/wasm/element.js")] diff --git a/examples/fetch/Cargo.toml b/examples/fetch/Cargo.toml index 4e59373c143..a76b418f8fe 100644 --- a/examples/fetch/Cargo.toml +++ b/examples/fetch/Cargo.toml @@ -8,10 +8,9 @@ edition = "2018" crate-type = ["cdylib"] [dependencies] -futures = "0.1.20" wasm-bindgen = { version = "0.2.50", features = ["serde-serialize"] } js-sys = "0.3.27" -wasm-bindgen-futures = "0.3.27" +wasm-bindgen-futures = "0.4.0" serde = { version = "1.0.80", features = ["derive"] } serde_derive = "^1.0.59" diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index f24627b70a1..e65bdf37b55 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -1,4 +1,3 @@ -use futures::{future, Future}; use js_sys::Promise; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; @@ -37,6 +36,10 @@ pub struct Signature { #[wasm_bindgen] pub fn run() -> Promise { + future_to_promise(run_()) +} + +async fn run_() -> Result { let mut opts = RequestInit::new(); opts.method("GET"); opts.mode(RequestMode::Cors); @@ -44,36 +47,25 @@ pub fn run() -> Promise { let request = Request::new_with_str_and_init( "https://api.github.com/repos/rustwasm/wasm-bindgen/branches/master", &opts, - ) - .unwrap(); + )?; request .headers() - .set("Accept", "application/vnd.github.v3+json") - .unwrap(); + .set("Accept", "application/vnd.github.v3+json")?; let window = web_sys::window().unwrap(); - let request_promise = window.fetch_with_request(&request); + let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; + + // `resp_value` is a `Response` object. + assert!(resp_value.is_instance_of::()); + let resp: Response = resp_value.dyn_into().unwrap(); - let future = JsFuture::from(request_promise) - .and_then(|resp_value| { - // `resp_value` is a `Response` object. - assert!(resp_value.is_instance_of::()); - let resp: Response = resp_value.dyn_into().unwrap(); - resp.json() - }) - .and_then(|json_value: Promise| { - // Convert this other `Promise` into a rust `Future`. - JsFuture::from(json_value) - }) - .and_then(|json| { - // Use serde to parse the JSON into a struct. - let branch_info: Branch = json.into_serde().unwrap(); + // Convert this other `Promise` into a rust `Future`. + let json = JsFuture::from(resp.json()?).await?; - // Send the `Branch` struct back to JS as an `Object`. - future::ok(JsValue::from_serde(&branch_info).unwrap()) - }); + // Use serde to parse the JSON into a struct. + let branch_info: Branch = json.into_serde().unwrap(); - // Convert this Rust `Future` back into a JS `Promise`. - future_to_promise(future) + // Send the `Branch` struct back to JS as an `Object`. + Ok(JsValue::from_serde(&branch_info).unwrap()) } diff --git a/examples/raytrace-parallel/Cargo.toml b/examples/raytrace-parallel/Cargo.toml index 3f7ed6a258a..a36129892e7 100644 --- a/examples/raytrace-parallel/Cargo.toml +++ b/examples/raytrace-parallel/Cargo.toml @@ -9,13 +9,13 @@ crate-type = ["cdylib"] [dependencies] console_error_panic_hook = "0.1" -futures = "0.1" js-sys = "0.3.27" rayon = "1.1.0" rayon-core = "1.5.0" raytracer = { git = 'https://github.com/alexcrichton/raytracer', branch = 'update-deps' } +futures-channel-preview = "0.3.0-alpha.18" wasm-bindgen = { version = "0.2.50", features = ['serde-serialize'] } -wasm-bindgen-futures = "0.3.27" +wasm-bindgen-futures = "0.4.0" [dependencies.web-sys] version = "0.3.23" diff --git a/examples/raytrace-parallel/src/lib.rs b/examples/raytrace-parallel/src/lib.rs index 413dbed5cc3..fbb9ad68374 100644 --- a/examples/raytrace-parallel/src/lib.rs +++ b/examples/raytrace-parallel/src/lib.rs @@ -1,5 +1,4 @@ -use futures::sync::oneshot; -use futures::Future; +use futures_channel::oneshot; use js_sys::{Promise, Uint8ClampedArray, WebAssembly}; use rayon::prelude::*; use wasm_bindgen::prelude::*; @@ -90,9 +89,13 @@ impl Scene { }); drop(tx.send(rgb_data)); })?; - let done = rx - .map(move |_data| image_data(base, len, width, height).into()) - .map_err(|_| JsValue::undefined()); + + let done = async move { + match rx.await { + Ok(_data) => Ok(image_data(base, len, width, height).into()), + Err(_) => Err(JsValue::undefined()), + } + }; Ok(RenderingScene { promise: wasm_bindgen_futures::future_to_promise(done),