1#![cfg_attr(test, feature(linkage))]
25
26pub mod jsbox;
27
28use stdweb::unstable::TryFrom;
29use std::convert::Infallible;
30use std::marker::PhantomData;
31use std::mem::drop;
32use std::panic;
33use stdweb::{self, js, console};
34
35use crate::jsbox::*;
36
37#[derive(Clone)]
49pub struct RawPromise {
50 p: stdweb::Reference,
51}
52impl RawPromise {
53 pub fn from_reference(p: stdweb::Reference) -> Self {
58 Self { p }
59 }
60
61 pub fn from_value(v: stdweb::Value) -> Self {
68 Self { p: v.into_reference().expect("Value passed to RawPromise::from_value was not an object") }
69 }
70
71 pub fn js_obj(&self) -> &stdweb::Reference {
73 &self.p
74 }
75
76 pub fn new_resolved(v: stdweb::Value) -> Self {
80 let p = js!{ return Promise.resolve(@{v}); };
81 Self::from_reference(p.into_reference().expect("Promise.resolve did not return an object"))
82 }
83
84 pub fn new_rejected(v: stdweb::Value) -> Self {
88 let p = js!{ return Promise.reject(@{v}); };
89 Self::from_reference(p.into_reference().expect("Promise.resolve did not return an object"))
90 }
91
92 pub fn from_result(v: Result<stdweb::Value, stdweb::Value>) -> Self {
97 match v {
98 Ok(v) => Self::new_resolved(v),
99 Err(v) => Self::new_rejected(v),
100 }
101 }
102
103 pub fn then<F: FnOnce(Result<stdweb::Value, stdweb::Value>) -> stdweb::Value + Send + 'static>(&self, f: F) -> Self {
115 let invoke_cb = move |v: stdweb::Value, is_ok: bool| {
116 let v_res = if is_ok { Ok(v) } else { Err(v) };
117 return invoke_tried(|| f(v_res));
118 };
119 let promise_val = js!{
120 let invoke_cb = @{stdweb::Once(invoke_cb)};
121 return @{self.js_obj()}.then(function(v) {
122 return invoke_cb(v, true);
123 }, function(v) {
124 return invoke_cb(v, false);
125 })
126 };
127
128 let promise_ref = promise_val.into_reference().expect("promise.then did not return an object");
129 Self{p: promise_ref}
130 }
131
132 pub fn then_to_typed<TOk: Send, TErr: Send, F: FnOnce(Result<stdweb::Value, stdweb::Value>) -> PromiseResult<TOk, TErr> + Send + 'static>(&self, f: F) -> Promise<TOk, TErr> {
142 let cb = move |v: Result<stdweb::Value, stdweb::Value>| {
143 invoke_tried(|| promise_result_to_value(f(v)))
144 };
145 Promise {
146 p: self.then(cb),
147 has_consumer: false,
148 _ph: Default::default()
149 }
150 }
151
152 pub fn all<I: IntoIterator<Item=RawPromise>>(promises: I) -> RawPromise {
157 let promise_objs = promises.into_iter()
158 .map(|promise| promise.p)
159 .collect::<Vec<_>>();
160 let promise_val = js!{
161 return Promise.all(@{promise_objs});
162 };
163 Self::from_value(promise_val)
164 }
165
166 pub fn all_settled<I: IntoIterator<Item=RawPromise>>(promises: I) -> RawPromise {
170 let promise_objs = promises.into_iter()
171 .map(|promise| promise.p)
172 .collect::<Vec<_>>();
173 let promise_val = js!{
174 return Promise.allSettled(@{promise_objs});
175 };
176 Self::from_value(promise_val)
177 }
178
179 pub fn race<I: IntoIterator<Item=RawPromise>>(promises: I) -> RawPromise {
184 let promise_objs = promises.into_iter()
185 .map(|promise| promise.p)
186 .collect::<Vec<_>>();
187 let promise_val = js!{
188 return Promise.race(@{promise_objs});
189 };
190 Self::from_value(promise_val)
191 }
192}
193
194
195pub struct Promise<TOk: Send+'static, TErr: Send+'static> {
219 p: RawPromise,
220 has_consumer: bool,
221 _ph: PhantomData<fn(TOk, TErr)>,
222}
223impl<TOk: Send+'static, TErr: Send+'static> Promise<TOk, TErr> {
224 pub fn then<TNewOk: Send+'static, TNewErr: Send+'static, F: FnOnce(Result<TOk, TErr>) -> PromiseResult<TNewOk, TNewErr> + Send + 'static>(mut self, f: F) -> Promise<TNewOk, TNewErr> {
230 let cb = move |res: Result<stdweb::Value, stdweb::Value>| {
231 let unboxed_res = match res {
232 Ok(raw_v) => {
233 if raw_v == stdweb::Value::Null {
234 return stdweb::Value::Null;
236 }
237
238 let unboxed_maybe = unsafe { js_unbox::<TOk>(raw_v.clone()) };
239 let unboxed = match unboxed_maybe {
240 Some(v) => v,
241 None => {
242 console!(error, "Could not unbox rust value from promise result. This is probably a bug with the js-promises crate. Value:", raw_v);
243 return stdweb::Value::Null;
244 }
245 };
246 Ok(unboxed)
247 },
248 Err(raw_v) => {
249 if raw_v == stdweb::Value::Null {
250 return stdweb::Value::Null;
252 }
253
254 let unboxed_maybe = unsafe { js_unbox::<TErr>(raw_v.clone()) };
255 let unboxed = match unboxed_maybe {
256 Some(v) => v,
257 None => {
258 console!(error, "Could not unbox rust value from promise result. This is probably a bug with the js-promises crate. Value:", raw_v);
259 return stdweb::Value::Null;
260 }
261 };
262 Err(unboxed)
263 },
264 };
265
266 return invoke_tried(|| promise_result_to_value(f(unboxed_res)));
267 };
268 self.has_consumer = true;
269 Promise {
270 p: self.p.then(cb),
271 has_consumer: false,
272 _ph: Default::default(),
273 }
274 }
275
276 pub fn new_resolved(v: TOk) -> Self {
278 Self {
279 p: RawPromise::new_resolved(js_box(v)),
280 has_consumer: false,
281 _ph: Default::default(),
282 }
283 }
284
285 pub fn new_rejected(v: TErr) -> Self {
287 Self {
288 p: RawPromise::new_rejected(js_box(v)),
289 has_consumer: false,
290 _ph: Default::default(),
291 }
292 }
293
294 pub fn from_result(v: Result<TOk, TErr>) -> Self {
299 match v {
300 Ok(v) => Self::new_resolved(v),
301 Err(v) => Self::new_rejected(v),
302 }
303 }
304
305
306 pub fn then_to_raw<F: FnOnce(Result<TOk, TErr>) -> stdweb::Value + Send + 'static>(mut self, f: F) -> RawPromise {
311 let cb = move |v: Result<stdweb::Value, stdweb::Value>| {
312 let unboxed_res = match v {
313 Ok(v) => unsafe { Ok(js_unbox::<TOk>(v).expect("Promise::then got invalid box to cb")) },
314 Err(v) => unsafe { Err(js_unbox::<TErr>(v).expect("Promise::then got invalid box to cb")) },
315 };
316 return (f)(unboxed_res);
317 };
318 self.has_consumer = true;
319 self.p.then(cb)
320 }
321
322 pub fn map<TNewOk: Send+'static, F: FnOnce(TOk) -> TNewOk + Send + 'static>(self, f: F) -> Promise<TNewOk, TErr> {
326 self.then(move |v: Result<TOk, TErr>| v.map(f).map(|v| v.into()))
327 }
328
329 pub fn map_err<TNewErr: Send+'static, F: FnOnce(TErr) -> TNewErr + Send + 'static>(self, f: F) -> Promise<TOk, TNewErr> {
333 self.then(move |v: Result<TOk, TErr>| v.map_err(f).map(|v| v.into()))
334 }
335
336 pub fn and_then<TNewOk: Send+'static, F: FnOnce(TOk) -> PromiseResult<TNewOk, TErr> + Send + 'static>(self, f: F) -> Promise<TNewOk, TErr> {
340 self.then(move |v: Result<TOk, TErr>| {
341 let v = match v {
342 Ok(v) => v,
343 Err(err) => { return Err(err).into(); },
344 };
345 (f)(v)
346 })
347 }
348
349 pub fn all_settled<I: IntoIterator<Item=Self>>(promises: I) -> Promise<Vec<Result<TOk, TErr>>, Infallible> {
356 let promise_objs = promises.into_iter()
357 .map(|mut promise| {
358 promise.has_consumer = true;
359 promise.p.js_obj().clone()
360 })
361 .collect::<Vec<_>>();
362
363 RawPromise::from_value(js!{
364 return Promise.allSettled(@{promise_objs});
365 }).then_to_typed(|res| {
366 let array = stdweb::Array::try_from(
367 res
368 .expect("Promise from Promise.allSettled was rejected")
369 .into_reference()
370 .expect("Value from Promise.allSettled was not a reference")
371 ).expect("Value from Promise.allSettled was not an array");
372
373 let value_vec = Vec::from(array);
374
375 Ok(value_vec.into_iter()
376 .map(|val| {
377 let status_val = js!{ return @{val.clone()}.status; };
378 let status = status_val.as_str().expect("Promise.allSettled entry.status was not a string");
379 match status {
380 "fulfilled" => {
381 let v = js!{ return @{val}.value; };
382 unsafe { Ok(js_unbox::<TOk>(v).expect("Promise.allSettled got invalid box to cb")) }
383 },
384 "rejected" => unsafe {
385 let v = js!{ return @{val}.reason; };
386 Err(js_unbox::<TErr>(v).expect("Promise.allSettled got invalid box to cb"))
387 },
388 _ => { panic!("Promise.allSettled entry.status was not 'fulfilled' or 'rejected', was {:?}", status); }
389 }
390 })
391 .collect::<Vec<_>>()
392 .into()
393 )
394 })
395 }
396}
397impl<TOk: Send, TErr: Send> Drop for Promise<TOk, TErr> {
398 fn drop(&mut self) {
399 if self.has_consumer {
402 return;
403 }
404 self.p.then(|v: Result<stdweb::Value, stdweb::Value>| {
405 match v {
406 Ok(v) => drop(unsafe { js_unbox::<TOk>(v) }),
407 Err(v) => drop(unsafe { js_unbox::<TErr>(v) }),
408 }
409 return stdweb::Value::Null;
410 });
411 }
412}
413
414pub type PromiseResult<TOk, TErr> = Result<PromiseOk<TOk, TErr>, TErr>;
419
420pub enum PromiseOk<TOk: Send+'static, TErr: Send+'static> {
425 Immediate(TOk),
426 Promise(Promise<TOk, TErr>),
427}
428impl<TOk: Send, TErr: Send> From<TOk> for PromiseOk<TOk, TErr> {
429 fn from(v: TOk) -> Self {
430 Self::Immediate(v)
431 }
432}
433impl<TOk: Send, TErr: Send> From<Promise<TOk, TErr>> for PromiseOk<TOk, TErr> {
434 fn from(v: Promise<TOk, TErr>) -> Self {
435 Self::Promise(v)
436 }
437}
438
439fn promise_result_to_value<TOk: Send, TErr: Send>(v: PromiseResult<TOk, TErr>) -> stdweb::Value {
441 match v {
442 Ok(PromiseOk::Immediate(v)) => js!{ return Promise.resolve(@{js_box(v)}) },
443 Ok(PromiseOk::Promise(mut p)) => {
444 p.has_consumer = true;
445 p.p.js_obj().into()
446 },
447 Err(v) => js!{ return Promise.reject(@{js_box(v)}) },
448 }
449}
450
451fn invoke_tried<F: FnOnce() -> stdweb::Value + 'static>(f: F) -> stdweb::Value {
452 return js!{
456 try {
457 return @{stdweb::Once(f)}();
458 } catch(e) {
459 console.error("Rust future callback panicked or threw JS error:", e);
460 return null;
461 }
462 };
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use serde_json;
469 use std::fmt::Display;
470 use std::string::ToString;
471 use stdweb::async_test;
472 use stdweb::console;
473
474 fn test_promise<TOk: Send, TErr: Send, FDone>(p: Promise<TOk, TErr>, done: FDone)
475 where
476 TErr: Display,
477 FDone: FnOnce(Result<(), String>) + Send + 'static,
478 {
479 p.then_to_raw(move |res| {
480 let res = res
481 .map(|_| ())
482 .map_err(|e| format!("Promise rejected: {}", e));
483 done(res);
484 return stdweb::Value::Null;
485 });
486 }
487
488 fn test_raw_promise<FDone>(p: RawPromise, done: FDone)
489 where
490 FDone: FnOnce(Result<(), String>) + Send + 'static,
491 {
492 p.then(move |res| {
493 let res = res
494 .map(|_| ())
495 .map_err(|e| format!("Promise rejected with value {}", js_to_string(e)));
496 done(res);
497 return stdweb::Value::Null;
498 });
499 }
500
501 fn reject_raw<Why: ToString>(why: Why) -> stdweb::Value {
502 return js!{ return Promise.reject(@{why.to_string()}); };
503 }
504
505 fn js_to_string(v: stdweb::Value) -> String {
506 return js!{ return JSON.stringify(@{v}); }.into_string().unwrap();
507 }
508
509 #[async_test]
510 fn raw_promise_new_resolved<F: FnOnce(Result<(), String>)>(done: F) {
511 let p = RawPromise::new_resolved(123u32.into())
512 .then(|res| {
513 let value = match res {
514 Ok(v) => v,
515 Err(_) => {
516 return reject_raw("new_resolved was rejected");
517 }
518 };
519 if value != stdweb::Value::Number(123u32.into()) {
520 return reject_raw(format!("new_resolved expected 123, got {}", js_to_string(value)));
521 }
522 return stdweb::Value::Null;
523 });
524 test_raw_promise(p, done);
525 }
526
527 #[async_test]
528 fn raw_promise_new_rejected<F: FnOnce(Result<(), String>)>(done: F) {
529 let p = RawPromise::new_rejected("oof".into())
530 .then(|res| {
531 let reason = match res {
532 Ok(_) => {
533 return reject_raw("new_rejected was resolved");
534 },
535 Err(v) => v,
536 };
537 if reason.as_str() != Some("oof") {
538 return reject_raw(format!("new_rejected expected 'oof', got {}", js_to_string(reason)));
539 }
540 return stdweb::Value::Null;
541 });
542 test_raw_promise(p, done);
543 }
544
545 #[async_test]
546 fn promise_new_resolved<F: FnOnce(Result<(), String>)>(done: F) {
547 let p = Promise::<String, String>::new_resolved("hello world".into())
548 .then(|res| {
549 let value: String = match res {
550 Ok(v) => v,
551 Err(_) => {
552 return Err(String::from("new_resolved was rejected"));
553 }
554 };
555 if value.as_str() != "hello world" {
556 return Err(format!("new_resolved expected 'hello world', got {:?}", value));
557 }
558 return Ok(PromiseOk::Immediate(()));
559 });
560 test_promise(p, done);
561 }
562
563 #[async_test]
564 fn promise_new_rejected<F: FnOnce(Result<(), String>)>(done: F) {
565 let p = Promise::<String, String>::new_rejected("oof".into())
566 .then(|res| {
567 let value: String = match res {
568 Err(v) => v,
569 Ok(_) => {
570 return Err(String::from("new_rejected was resolved"));
571 }
572 };
573 if value.as_str() != "oof" {
574 return Err(format!("new_rejected expected 'oof', got {:?}", value));
575 }
576 return Ok(PromiseOk::Immediate(()));
577 });
578 test_promise(p, done);
579 }
580
581 fn http_req(url: &str) -> Promise<serde_json::Value,String> {
582 let req = stdweb::web::XmlHttpRequest::new();
583 req.open("GET", url).expect("open failed");
584
585 let p = RawPromise::from_value(js!{
586 return new Promise(function(resolve, reject) {
587 let req = @{req.as_ref().clone()};
588 req.addEventListener("load", resolve);
589 req.addEventListener("abort", function() { reject("abort"); });
590 req.addEventListener("error", function() { reject("error"); });
591 req.addEventListener("timeout", function() { reject("timeout"); });
592 });
593 });
594 req.send().unwrap();
595 let p = p.then_to_typed::<serde_json::Value, String, _>(move |res| {
596 let _ev = res.map_err(|v| v.into_string().unwrap())?;
597
598 let body = req.response_text().unwrap().unwrap();
599 let out = serde_json::from_str::<serde_json::Value>(&body).unwrap();
600 return Ok(PromiseOk::Immediate(out));
601 });
602
603 return p;
604 }
605
606 #[async_test]
607 fn ajax_request<F: FnOnce(Result<(), String>)>(done: F) {
608 let p = http_req("https://httpbin.org/get")
609 .and_then(|v| {
610 let url_v = v.pointer("/url").ok_or(String::from("Expected url field in response"))?;
611 let url = url_v.as_str().ok_or(String::from("url field in response was not a string"))?;
612 if url == "https://httpbin.org/get" {
613 return Ok(PromiseOk::Immediate(()));
614 } else {
615 return Err(format!("url field in response was incorrect, had value {:?}", url));
616 }
617 });
618 test_promise(p, done);
619 }
620
621 #[async_test]
622 fn return_a_promise<F: FnOnce(Result<(), String>)>(done: F) {
623 let p = Promise::<String,String>::new_resolved(String::from("https://httpbin.org/get"))
624 .and_then(|url| {
625 console!(log, "url:", &url);
626 return Ok(PromiseOk::Promise(http_req(&url)));
627 })
628 .and_then(|v| {
629 let url_v = v.pointer("/url").ok_or(String::from("Expected url field in response"))?;
630 let url = url_v.as_str().ok_or(String::from("url field in response was not a string"))?;
631 if url == "https://httpbin.org/get" {
632 return Ok(PromiseOk::Immediate(()));
633 } else {
634 return Err(format!("url field in response was incorrect, had value {:?}", url));
635 }
636 });
637 test_promise(p, done);
638 }
639}