Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Are there any mechanisms to pass a result of Promise into WASM(Rust) code? And how do you use it? #138

Closed
y-ich opened this issue Feb 27, 2018 · 31 comments

Comments

@y-ich
Copy link

y-ich commented Feb 27, 2018

Hi.

I want to pass a result of JS Promise into Rust code like the below.

let result = js! { await asyncFunc(@{some_value}) };

I tried such a code but got an error "SyntaxError: Unexpected identifier 'asyncFunc'" when loading generated js file.

I should use rather Promise but I have no idea how I can take the result out from js! macro.

@kngwyu
Copy link
Contributor

kngwyu commented Feb 27, 2018

stdweb has PromiseFuture.
In your Cargo.toml

[dependencies]
futures = "0.1"
[dependencies.stdweb]
version = "0.4"
features = ["experimental_features_which_may_break_on_minor_version_bumps"]

and

use stdweb::{Promise, PromiseFuture};
use futures::Future;
let promise = Promise::from_thenable(&js! {
    return new Promise(function (resolve, reject) {
        // some code
    })}.try_into()
        .unwrap());
let future = promise.unwrap().to_future().

will work, though it's experimental (see #127).
#103 contains examples.

In addition, I think ES2017 is not yet supported.

@y-ich
Copy link
Author

y-ich commented Feb 27, 2018

Thank you!

I might write a code for Promise, but I have next trouble. Can I keep this issue as my questions?

I want to pass Float32Array from Rust code to JS in js! macro statement.
Here is a simplified code and I got a panic message at the js! macro statement on browser.

let mut a_variable: [f32; N];
... // some processes
let a_argument = unsafe { UnsafeTypedArray::new(&a_variable) };
let result = js! { evaluate(@{ a_argument }) };

Is using UnsafeTypedArray for serialize wrong?
How can I pass Float32Array?

Sorry for bothering you.
Thank you for your help.

@Pauan
Copy link
Contributor

Pauan commented Feb 27, 2018

@y-ich For using Promises, this is how your code should be:

let future: PromiseFuture<SomeType> = js!( return asyncFunc(@{some_value}); ).try_into().unwrap();

SomeType is the type of the resolved value for the Promise. You have to specify the type so that stdweb knows how to serialize the JavaScript value.

For example, if the JavaScript Promise resolves to a String then you should use the type PromiseFuture<String>.

Or if the JavaScript Promise resolves to an Array of Numbers, then you should use the type PromiseFuture<Vec<i32>> or PromiseFuture<Vec<f64>>, etc.

After that you can import the futures crate and then use all of the Rust Future methods on it:

let future = future.map(|x| { ... }).and_then(|x| { ... });

And finally you can spawn the Future, which causes it to actually be run:

use stdweb::PromiseFuture;

PromiseFuture::spawn(
    future.map_err(|e| console!(error, e))
);

You should generally only call spawn once, inside of your main function.

@koute
Copy link
Owner

koute commented Feb 27, 2018

@y-ich The UnsafeTypedArray snippet you've pasted should work, I think. That's its intended purpose. (Although you should read UnsafeTypedArray's docs as there is a significant caveat in using it.) If you're still getting a panic you should post the exact error you're getting.

@y-ich
Copy link
Author

y-ich commented Feb 27, 2018

@pauen san's first point is my problem! Actually I have missed return statement in js! macro.
I guess I could pass UnsafeTypedArray.

Now I got next panic in the wait method of Future...
I don't understand the role of PromiseFuture::spawn yet, I will study it.

Thank you for you guys!

@kngwyu
Copy link
Contributor

kngwyu commented Feb 27, 2018

@y-ich
spawn is needed to set notifier for your PromiseFuture, so if you don't spawn, you can't know its result.
In addition, there seems to be no way to use wait method, because of PromiseFuture::spawn's design.
Instead, you can use map or other methods.
Here's simple ajax example.

@Pauan
Is my understanding correct?

@Pauan
Copy link
Contributor

Pauan commented Feb 27, 2018

@kngwyu Yes, you're correct.

Because of limitations in JavaScript it is impossible to use .wait(), so you must use .map(|x| ...) or .inspect(|x| ...) to retrieve the value of the Future (and of course you must use PromiseFuture::spawn to actually run the Future at all!)

As for your example, it's not necessary to use from_thenable, using PromiseFuture alone should be enough:

fn read_static_file(fname: &str) -> PromiseFuture<ArrayBuffer> {
    // create promise wrapping Ajax
    js! (
        return new Promise(function (resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.onload = function() {
                if (xhr.status == 200) {
                    console.log("got reponse");
                    resolve(xhr.response);
                } else {
                    reject(new Error(xhr.statusText));
                }
            };
            xhr.open("GET", @{fname});
            xhr.responseType = "arraybuffer";
            xhr.onerror = reject;
            xhr.send();
        });
    ).try_into().unwrap()
}

Also, if the Promise gives an error, I recommend using console!(error, err) rather than using panic!:

let future = read_static_file(FILE_PATH)
    .map(move |res| {
        let txt: Vec<u8> = res.into();
        println!("a.txt: {}", str::from_utf8(&txt).unwrap());
    })
    .map_err(|err| console!(error, err));

But other than that, your example looks good! Is it okay if we use your example in stdweb?

@kngwyu
Copy link
Contributor

kngwyu commented Feb 27, 2018

@Pauan
It's totally OK.
Thanks for your advice.

@y-ich
Copy link
Author

y-ich commented Feb 27, 2018

Umm, I am shocked because my Rust code is totally designed assuming that it gets return values from wrapper function of js!...

You can not see the return value in the thread that spawns the promisefuture.
You can see the value in the thread that is spawned.
Is that right?

@y-ich
Copy link
Author

y-ich commented Feb 27, 2018

I tried to use Channel but don't suceeded.

let js_promise: js!( return asyncFunc(@{some_value}); ).try_into().unwrap();
let promise = Promise::from_thenable(&js_promise).unwrap();
let future: PromiseFuture<SomeType, Error> = promise.to_future();
let (tx, rx) = oneshot::channel();
let future = future
    .map(|x| { tx.send(x); })
    .map_err(|e| { console!(error, e); });
PromiseFuture::spawn(future);
let someType = rx.recv().unwrap();

I got an error "no method named recv found for type futures::Receiver<SomeType> in the current scope".

I am not quite sure about threads on stdweb Process and PromiseFuture.
Is this wrong way?

@kngwyu
Copy link
Contributor

kngwyu commented Feb 27, 2018

@y-ich
Did you read future's document?

Anyway, it seems impossible to send messages to main thread using future's oneshot channel, though I don't know technical details, too.(Edited: I had a misunderstanding that wasm can use WebWorker!)
I got a error couldn't send string!: "Hello!\n" by this code(Hello!\n is what FILE_PATH contains).

fn main() {
    stdweb::initialize();
    let (p, c) = oneshot::channel::<String>();
    let future = read_static_file(FILE_PATH)
        .map(|res| {
            let txt: Vec<u8> = res.into();
            let s = str::from_utf8(&txt).unwrap();
            p.send(s.to_owned()).expect("couldn't send string!");
        })
        .map_err(|err| console!(error, err));
    PromiseFuture::spawn(future);
    let _ = c.map(|s| println!("a.txt: {}", s));
    stdweb::event_loop();
}

@Pauan
Copy link
Contributor

Pauan commented Feb 27, 2018

@y-ich You can not see the return value in the thread that spawns the promisefuture.
You can see the value in the thread that is spawned.
Is that right?

JavaScript is single-threaded, but what you said is basically correct: JavaScript does not allow you to block the main thread, which is why .wait() does not work.

This is a limitation in all JavaScript programs, so it is impossible for stdweb to fix it.

So if you want to do an asynchronous operation then you must use the .map or .inspect methods, like this:

fn main() {
    let future: PromiseFuture<SomeType> = js!( return asyncFunc(@{some_value}); ).try_into().unwrap();

    PromiseFuture::spawn(
        future
            .map(|value| {
                // We can access the future's value inside of this closure
                do_something_with_value(value);
            })
            .map_err(|e| console!(error, e))
    );
}

If you need to use the result of future then you must put your code inside of the .map closure.

Also, you don't need to use channels or from_thenable, they are only used in very rare situations. The above code should be good enough for normal uses.

@y-ich
Copy link
Author

y-ich commented Feb 27, 2018

Thank you for your comments.
It is my first stdweb project and may be a little more advanced level before trying tutorials^^

This limitation is pretty interesting, it is like Monad in Haskell.

Thank you.
Since I got the point, I close this issue.

@y-ich y-ich closed this as completed Feb 27, 2018
@Pauan
Copy link
Contributor

Pauan commented Feb 27, 2018

@y-ich Yes! It's exactly like Monad in Haskell.

JavaScript Promises, Rust Futures, and Haskell Monads are all very similar to each other.

As an example, foo.and_then(|x| bar(x)) in Rust is the same as foo >>= (\x -> bar x) in Haskell.

I wish you good luck with learning. If you ever have any more questions, please ask us and we will do our best to help.

@y-ich y-ich changed the title [Question] Are there any mechanisms to pass a result of Promise into WASM(Rust) code? [Question] Are there any mechanisms to pass a result of Promise into WASM(Rust) code? And how do you use it? Feb 27, 2018
@y-ich
Copy link
Author

y-ich commented Feb 27, 2018

@Pauan san, thank you for your kind word!

@y-ich
Copy link
Author

y-ich commented Feb 28, 2018

Just self follow-up.
I found async/await macro in Rust. https://github.com/alexcrichton/futures-await

So I may be able to avoid explicit Monad world^^

@Pauan
Copy link
Contributor

Pauan commented Feb 28, 2018

@y-ich Yes, async-await is very nice, however it is currently only supported on Nightly Rust.

So if you want your code to work on Beta or Stable then unfortunately you'll need to use the map / and_then / etc. methods.

@y-ich
Copy link
Author

y-ich commented Feb 28, 2018

@Pauan san, thank you for your advice. Since I love box syntax, I am usually using nightly Rust^^

I have a next question.
Now I am trying to use futures-await.
And I need to pass Result type as a return value of Rust function to JavaScript since #[async] function must return Result.
Can I serialize Result?

Here is a signature of my function.

#[derive(Serialize)]
pub struct SomeType {
    ...
}

js_serializable!(SomeType);

#[js_export]
#[async]
pub fn js_call_this() -> Result<SomeType, stdweb::Error> {}

and I got an error below.

error[E0277]: the trait bound `stdweb::private::Newtype<_, impl futures::__rt::MyFuture<std::result::Result<SomeType, [type error]>>>: stdweb::private::JsSerializeOwned` is not satisfied
  --> src/lib.rs:26:1
   |
26 | #[js_export]
   | ^^^^^^^^^^^^ the trait `stdweb::private::JsSerializeOwned` is not implemented for `stdweb::private::Newtype<_, impl futures::__rt::MyFuture<std::result::Result<SomeType, [type error]>>>`
   |
   = help: the following implementations were found:
             <stdweb::private::Newtype<(stdweb::webcore::serialization::FunctionTag, (A9, A10, A11, A12)), std::option::Option<F>> as stdweb::private::JsSerializeOwned>
             <stdweb::private::Newtype<(stdweb::webcore::serialization::FunctionTag, (A4, A5, A6, A7, A8, A9, A10, A11, A12)), std::option::Option<F>> as stdweb::private::JsSerializeOwned>
             <stdweb::private::Newtype<(stdweb::webcore::serialization::FunctionTag, (A11, A12)), std::option::Option<F>> as stdweb::private::JsSerializeOwned>
             <stdweb::private::Newtype<(stdweb::webcore::serialization::FunctionTag, (A3, A4, A5, A6, A7, A8, A9, A10, A11, A12)), std::option::Option<F>> as stdweb::private::JsSerializeOwned>
           and 39 others
   = note: required by `stdweb::private::JsSerializeOwned::memory_required_owned`

I can understand that I need to serialize Result, but Result is not mine.
Do I need to define my own Result?

Thanks.

@y-ich y-ich reopened this Feb 28, 2018
@kngwyu
Copy link
Contributor

kngwyu commented Feb 28, 2018

@y-ich
Look at compile errors carefully!
#[async] macro rewrite your function to return impl futures::__rt::MyFuture, so

impl JsSerializeOwned for futures::__rt::MyFuture<std::result::Result<SomeType, [type error]>> {}

is needed(though I don't know we can implement it..)(and here's impl trait's document).

In addition, I think it's impossible to use await! macro to PromiseFuture bacause of lifetime problem, in current API design.

@y-ich
Copy link
Author

y-ich commented Feb 28, 2018

@kngwyu san, I am sorry that I am using this github issue like Stack Overflow.
Honestly, I use this issue to save my time.
If no one try async/await crate on stdweb yet, there must be several problems and they may be hard for me.
Your last sentence saves my time a lot!
Thank you!

@y-ich y-ich closed this as completed Feb 28, 2018
@kngwyu
Copy link
Contributor

kngwyu commented Feb 28, 2018

@y-ich
Oh, I'm sorry if you felt bad.
stdweb has gitter channel.
So, if you have a question and you feel github issue isn't suitable for it, you can question at gitter.

@y-ich
Copy link
Author

y-ich commented Feb 28, 2018

@kngwyu san.
I have never known it. I will post questions to the channel next time!
Thank you!

@Pauan
Copy link
Contributor

Pauan commented Mar 2, 2018

PromiseFuture::spawn has a 'static lifetime requirement, but that simply means that the Future cannot contain any references to variables on the stack.

But any closures inside of the Future can have local references, because they have their own stack.

I just now tested it, and async-await works perfectly fine with PromiseFuture::spawn:

#![feature(proc_macro, conservative_impl_trait, generators)]

#[macro_use]
extern crate stdweb;

extern crate futures_await as futures;

use futures::prelude::*;

use stdweb::PromiseFuture;
use stdweb::web::error::Error;
use stdweb::unstable::TryInto;


// DO NOT USE THIS!
// This does NOT support cancellation! It is used ONLY as an example!
fn wait() -> PromiseFuture<()> {
    js!(
        return new Promise(function (success, error) {
            setTimeout(function () {
                success();
            }, 1000);
        });
    ).try_into().unwrap()
}


#[async]
fn testing() -> Result<(), Error> {
    let mut foo: Vec<u32> = vec![1, 2, 3];

    {
        let r = &foo;
        println!("{:#?}", r);
    }

    await!(wait())?;

    foo.push(4);

    {
        let r = &foo;
        println!("{:#?}", r);
    }

    await!(wait())?;

    foo.push(5);

    {
        let r = &foo;
        println!("{:#?}", r);
    }

    Ok(())
}


fn main() {
    PromiseFuture::spawn(
        testing().map_err(|e| console!(error, e))
    );
}

@y-ich It's true that stdweb cannot serialize Result, however in your error message it says that it's trying to serialize a MyFuture, not a Result.

Could you please give your full code in a gist, so that way I can see what the problem is?

@kngwyu
Copy link
Contributor

kngwyu commented Mar 2, 2018

Oh, I had no idea to spawn __rt::MyFuture.
I'm sorry for incorrect comment, and @Pauan, thanks for good demonstration!

@y-ich
Copy link
Author

y-ich commented Mar 2, 2018

@Pauan san,
Here is a simplified code that I want to implement.
https://github.com/y-ich/stdweb_async.git

Now I have no idea to return back a value to JS since PromiseFuture::spawn has no return values.
Thank you.

@y-ich
Copy link
Author

y-ich commented Mar 3, 2018

I succeeded to return back a value to JS by a tricky resolve function.
https://github.com/y-ich/stdweb_async/tree/solved

I will apply this method to my real project anyway.

Thank you for you guys' many helps!

@Pauan
Copy link
Contributor

Pauan commented Mar 5, 2018

@y-ich Ahh, I understand now what you are trying to do.

We do not currently have a way to convert from a Rust Future into a JavaScript Promise.

But I am now working on adding in a Promise::from_future function.


@koute Is it possible to have higher-order callbacks with the js! macro? For example, like this:

let callback = move |success: FnOnce( A::Item ), error: FnOnce( A::Error )| {
    ...
};

js!( ... @{Once( callback )} ... ).try_into().unwrap()

@Pauan
Copy link
Contributor

Pauan commented Mar 5, 2018

@y-ich I now created a pull request which adds in a Promise::from_future function.

After that pull request is accepted and after stdweb releases a new version, you will be able to do this:

#![feature(proc_macro, conservative_impl_trait, generators)]

extern crate futures_await as futures;
#[macro_use]
extern crate stdweb;

use futures::prelude::*;
use stdweb::{Promise, PromiseFuture, js_export};
use stdweb::web::error::Error;
use stdweb::unstable::TryInto;

fn from_js_async() -> PromiseFuture<i32> {
    js!( return js_async(); ).try_into().unwrap()
}

/// rust_async should return a Promise that returns a retun value of a Promise of js_async defined in main.js.
#[js_export]
fn rust_async() -> Promise {
    Promise::from_future(from_js_async())
}

Now in JavaScript you can call rust_async() which will return a JavaScript Promise.

If you want to use the from_js_async Future in Rust, you can use #[async] functions like normal:

#![feature(proc_macro, conservative_impl_trait, generators)]

extern crate futures_await as futures;
#[macro_use]
extern crate stdweb;

use futures::prelude::*;
use stdweb::{Promise, PromiseFuture, js_export};
use stdweb::web::error::Error;
use stdweb::unstable::TryInto;

fn from_js_async() -> PromiseFuture<i32> {
    js!( return js_async(); ).try_into().unwrap()
}

#[async]
fn use_js_async() -> Result<i32, Error> {
    let a = await!(from_js_async())?;

    // use the result of from_js_async in here

    Ok(a)
}

/// rust_async should return a Promise that returns a retun value of a Promise of js_async defined in main.js.
#[js_export]
fn rust_async() -> Promise {
    Promise::from_future(use_js_async())
}

Notice that the rust_async function is using use_js_async(), not from_js_async()

@y-ich
Copy link
Author

y-ich commented Mar 5, 2018

@Pauan san, it is cool!

@Pauan
Copy link
Contributor

Pauan commented Mar 27, 2018

@y-ich stdweb 0.4.2 now has support for Promise::from_future.

You can use it like how I showed in my previous message.

@y-ich
Copy link
Author

y-ich commented Mar 27, 2018

@Pauan san, thank you for your notification!

I have another problem (alexcrichton/futures-await#11) in my app about futures-await and it seems to be resolved in next version(0.2.0-alpha).

And 0.2.0-alpha futures, that future-await 0.2.0-alpha uses, and 0.1.18 futures, that stdweb uses, seem to has a conflict for derive macro though I don't understand well.

So I am looking forward to the release of 0.2.0 of futures and futures-await and adoption of it by stdweb.

I leave you contributers to decide to close this issue.
Thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants