diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 60d0502a..27397df0 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -30,6 +30,7 @@ - [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md) - [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md) - [`Promise`s and `Future`s](./reference/js-promises-and-rust-futures.md) + - [Iterating over JS Values](./reference/iterating-over-js-values.md) - [No ES Modules](./reference/no-esm.md) - [Arbitrary Data with Serde](./reference/arbitrary-data-with-serde.md) - [Accessing Properties of Untyped JS Values](./reference/accessing-properties-of-untyped-js-values.md) diff --git a/guide/src/reference/iterating-over-js-values.md b/guide/src/reference/iterating-over-js-values.md new file mode 100644 index 00000000..73ef6d99 --- /dev/null +++ b/guide/src/reference/iterating-over-js-values.md @@ -0,0 +1,95 @@ +# Iterating over JavaScript Values + +## Methods That Return `js_sys::Iterator` + +Some JavaScript collections have methods for iterating over their values or +keys: + +* [`Map::values`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Map.html#method.values) +* [`Set::keys`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Set.html#method.keys) +* etc... + +These methods return +[`js_sys::Iterator`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Iterator.html), +which is the Rust representation of a JavaScript object that has a `next` method +that either returns the next item in the iteration, notes that iteration has +completed, or throws an error. That is, `js_sys::Iterator` represents an object +that implements [the duck-typed JavaScript iteration +protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). + +`js_sys::Iterator` can be converted into a Rust iterator either by reference +(into +[`js_sys::Iter<'a>`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.Iter.html)) +or by value (into +[`js_sys::IntoIter`](https://rustwasm.github.io/wasm-bindgen/api/js_sys/struct.IntoIter.html)). The +Rust iterator will yield items of type `Result`. If it yields an +`Ok(...)`, then the JS iterator protocol returned an element. If it yields an +`Err(...)`, then the JS iterator protocol threw an exception. + +```rust +extern crate js_sys; +extern crate wasm_bindgen; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn count_strings_in_set(set: &js_sys::Set) -> u32 { + let mut count = 0; + + // Call `keys` to get an iterator over the set's elements. Because this is + // in a `for ... in ...` loop, Rust will automatically call its + // `IntoIterator` trait implementation to convert it into a Rust iterator. + for x in set.keys() { + // We know the built-in iterator for set elements won't throw + // exceptions, so just unwrap the element. If this was an untrusted + // iterator, we might want to explicitly handle the case where it throws + // an exception instead of returning a `{ value, done }` object. + let x = x.unwrap(); + + // If `x` is a string, increment our count of strings in the set! + if x.is_string() { + count += 1; + } + } + + count +} +``` + +## Iterating Over Any JavaScript Object that Implements the Iterator Protocol + +You could manually test for whether an object implements JS's duck-typed +iterator protocol, and if so, convert it into a `js_sys::Iterator` that you can +finally iterate over. You don't need to do this by-hand, however, since we +bundled this up as [the `js_sys::try_iter` +function!](https://rustwasm.github.io/wasm-bindgen/api/js_sys/fn.try_iter.html) + +For example, we can write a function that collects the numbers from any JS +iterable and returns them as an `Array`: + +```rust +extern crate js_sys; +extern crate wasm_bindgen; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn collect_numbers(some_iterable: &JsValue) -> Result { + let nums = js_sys::Array::new(); + + let iterator = match js_sys::try_iter(some_iterable)?.ok_or_else(|| { + "need to pass iterable JS values!".into() + })?; + + for x in iterator { + // If the iterator's `next` method throws an error, propagate it + // up to the caller. + let x = x?; + + // If `x` is a number, add it to our array of numbers! + if x.is_f64() { + nums.push(&x); + } + } + + Ok(nums) +} +```