From a1d90398d09af2ec67d6a39f12430e97e8b3214f Mon Sep 17 00:00:00 2001 From: Pauan Date: Thu, 5 Dec 2019 00:51:22 +0100 Subject: [PATCH] Adding in support for async iterators (#1895) * Adding in support for async iterators * Adding in some unit tests for asyncIterator * Fixing unit tests * Fixing UI tests --- crates/js-sys/src/lib.rs | 26 +++++++++++++++++ crates/js-sys/tests/wasm/Symbol.js | 35 +++++++++++++++++++++++ crates/js-sys/tests/wasm/Symbol.rs | 9 ++++++ crates/macro/ui-tests/async-errors.stderr | 2 +- 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs index a3e887ab..6dfa1bff 100644 --- a/crates/js-sys/src/lib.rs +++ b/crates/js-sys/src/lib.rs @@ -1349,6 +1349,25 @@ impl Iterator { } } +// Async Iterator +#[wasm_bindgen] +extern "C" { + /// Any object that conforms to the JS async iterator protocol. For example, + /// something returned by `myObject[Symbol.asyncIterator]()`. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) + #[derive(Clone, Debug)] + #[wasm_bindgen(is_type_of = Iterator::looks_like_iterator)] + pub type AsyncIterator; + + /// The next method always has to return a Promise which resolves to an object + /// with appropriate properties including done and value. If a non-object value + /// gets returned (such as false or undefined), a TypeError ("iterator.next() + /// returned a non-object value") will be thrown. + #[wasm_bindgen(catch, method, structural)] + pub fn next(this: &AsyncIterator) -> Result; +} + /// An iterator over the JS `Symbol.iterator` iteration protocol. /// /// Use the `IntoIterator for &js_sys::Iterator` implementation to create this. @@ -4165,6 +4184,13 @@ extern "C" { #[wasm_bindgen(static_method_of = Symbol, getter, structural, js_name = isConcatSpreadable)] pub fn is_concat_spreadable() -> Symbol; + /// The `Symbol.asyncIterator` well-known symbol specifies the default AsyncIterator for an object. + /// If this property is set on an object, it is an async iterable and can be used in a `for await...of` loop. + /// + /// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) + #[wasm_bindgen(static_method_of = Symbol, getter, structural, js_name = asyncIterator)] + pub fn async_iterator() -> Symbol; + /// The `Symbol.iterator` well-known symbol specifies the default iterator /// for an object. Used by `for...of`. /// diff --git a/crates/js-sys/tests/wasm/Symbol.js b/crates/js-sys/tests/wasm/Symbol.js index c24342c1..a66691fd 100644 --- a/crates/js-sys/tests/wasm/Symbol.js +++ b/crates/js-sys/tests/wasm/Symbol.js @@ -36,6 +36,41 @@ exports.test_iterator = function(sym) { assert.deepEqual([...iterable1], [1, 2, 3]); }; +exports.test_async_iterator = async function(sym) { + const iterable1 = new Object(); + + iterable1[sym] = function () { + let done = false; + + return { + next() { + if (done) { + return Promise.resolve({ + done: true, + value: 1 + }); + + } else { + done = true; + + return Promise.resolve({ + done: false, + value: 0 + }); + } + } + }; + }; + + const values = []; + + for await (let value of iterable1) { + values.push(value); + } + + assert.deepEqual(values, [0]); +}; + exports.test_match = function(sym) { const regexp1 = /foo/; assert.throws(() => '/foo/'.startsWith(regexp1)); diff --git a/crates/js-sys/tests/wasm/Symbol.rs b/crates/js-sys/tests/wasm/Symbol.rs index c48d5ddc..b4ed544e 100644 --- a/crates/js-sys/tests/wasm/Symbol.rs +++ b/crates/js-sys/tests/wasm/Symbol.rs @@ -1,12 +1,14 @@ use js_sys::*; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; +use wasm_bindgen_futures::JsFuture; #[wasm_bindgen(module = "tests/wasm/Symbol.js")] extern "C" { fn test_has_instance(sym: &Symbol); fn test_is_concat_spreadable(sym: &Symbol); fn test_iterator(sym: &Symbol); + fn test_async_iterator(sym: &Symbol) -> Promise; fn test_match(sym: &Symbol); fn test_replace(sym: &Symbol); fn test_search(sym: &Symbol); @@ -37,6 +39,11 @@ fn iterator() { test_iterator(&Symbol::iterator()); } +#[wasm_bindgen_test] +async fn async_iterator() { + JsFuture::from(test_async_iterator(&Symbol::async_iterator())).await.unwrap_throw(); +} + #[wasm_bindgen_test] fn match_() { test_match(&Symbol::match_()); @@ -89,12 +96,14 @@ fn key_for() { let sym = Symbol::for_("foo"); assert_eq!(Symbol::key_for(&sym), "foo"); assert!(Symbol::key_for(&Symbol::iterator()).is_undefined()); + assert!(Symbol::key_for(&Symbol::async_iterator()).is_undefined()); assert!(Symbol::key_for(&gensym(JsValue::undefined())).is_undefined()); } #[wasm_bindgen_test] fn to_string() { assert_eq!(Symbol::iterator().to_string(), "Symbol(Symbol.iterator)"); + assert_eq!(Symbol::async_iterator().to_string(), "Symbol(Symbol.asyncIterator)"); assert_eq!(Symbol::for_("foo").to_string(), "Symbol(foo)"); assert_eq!(gensym("desc".into()).to_string(), "Symbol(desc)"); } diff --git a/crates/macro/ui-tests/async-errors.stderr b/crates/macro/ui-tests/async-errors.stderr index cb734ada..118064fc 100644 --- a/crates/macro/ui-tests/async-errors.stderr +++ b/crates/macro/ui-tests/async-errors.stderr @@ -31,7 +31,7 @@ error[E0277]: the trait bound `wasm_bindgen::JsValue: std::convert::From> > > - and 61 others + and 62 others = note: required because of the requirements on the impl of `std::convert::Into` for `BadType` = note: required because of the requirements on the impl of `wasm_bindgen::__rt::IntoJsResult` for `BadType` = note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`