platter 0.1.4

Load files on web and desktop asynchronously
Documentation
use super::{new_wasm_error, web_try};
use futures_util::future::{poll_fn, ready, TryFutureExt};
use std::{
    future::Future,
    io::Error as IOError,
    task::{Context, Poll},
};
use stdweb::{
    traits::*,
    unstable::TryInto,
    web::{
        event::{ProgressAbortEvent, ProgressLoadEvent},
        ArrayBuffer, TypedArray, XhrReadyState, XhrResponseType, XmlHttpRequest,
    },
    Reference,
};

pub fn make_request(path: &str) -> impl Future<Output = Result<Vec<u8>, IOError>> {
    ready(create_request(path)).and_then(|xhr| {
        let mut have_set_handlers = false;
        poll_fn(move |ctx| poll_request(&xhr, ctx, &mut have_set_handlers))
    })
}

fn create_request(path: &str) -> Result<XmlHttpRequest, IOError> {
    let xhr = XmlHttpRequest::new();
    web_try(xhr.open("GET", path), "Failed to create a GET request")?;
    web_try(
        xhr.set_response_type(XhrResponseType::ArrayBuffer),
        "Failed to set the response type",
    )?;
    web_try(xhr.send(), "Failed to send a GET request")?;
    Ok(xhr)
}

fn poll_request(
    xhr: &XmlHttpRequest,
    ctx: &mut Context,
    have_set_handlers: &mut bool,
) -> Poll<Result<Vec<u8>, IOError>> {
    if !*have_set_handlers {
        *have_set_handlers = true;
        let waker = ctx.waker().clone();
        xhr.add_event_listener(move |_: ProgressLoadEvent| waker.wake_by_ref());
        let waker = ctx.waker().clone();
        xhr.add_event_listener(move |_: ProgressAbortEvent| waker.wake_by_ref());
    }
    let status = xhr.status();
    let ready_state = xhr.ready_state();
    match (status / 100, ready_state) {
        (2, XhrReadyState::Done) => {
            let reference: Reference = xhr
                .raw_response()
                .try_into()
                .expect("The response will always be a JS object");
            Poll::Ready(
                reference
                    .downcast::<ArrayBuffer>()
                    .map(|arr| TypedArray::<u8>::from(arr).to_vec())
                    .ok_or_else(|| new_wasm_error("Failed to cast file into bytes")),
            )
        }
        (2, _) => Poll::Pending,
        (0, _) => Poll::Pending,
        _ => Poll::Ready(Err(new_wasm_error("Non-200 status code returned"))),
    }
}