js_promises/
lib.rs

1//! Library for working with Javascript Promises using Rust code.
2//! 
3//! This crate contains two main types: `RawPromise`, which is a thin
4//! wrapper over JS's promise types, and `Promise`, which helps use
5//! promises with Rust types.
6//! 
7//! All methods that chain a promise currently translate to a `then` call, even if the
8//! result is available immediately. So doing as much synchronous work in one `then` call
9//! will be slightly more efficient than multiple `then` calls.
10//! 
11//! In promise callbacks, if a panic or JS error occurs, the library will print the error
12//! via `console.error` and return `null` from the promise.
13//! 
14//! Comparison to stdweb promises
15//! -----------------------------
16//! 
17//! * stdweb promises, at the time of writing, require a feature to access, and are unstable.
18//!   This... is also unstable but not locked behind a feature.
19//! * stdweb promises try to be compatible with the futures API, which does not map well to
20//!   how JS promises and callbacks work. This crate exposes an API that mirrors JS's promises
21//!   API.
22
23// Needed for #[async_test]
24#![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/// Wrapper for a JS promise, working with `stdweb::Value`s.
38/// 
39/// This provides a thin layer over JS promise objects, so that the user does not
40/// have to worry about using the `js!{}` macro for calling `then` or ensuring the rust
41/// callback functions are freed.
42/// 
43/// To wrap an existing promise created using `js!{}`, use the `from_reference` or `from_value`
44/// functions. You can also use `new_resolved` or `new_rejected` to create pre-resolved promises.
45/// 
46/// Note that if the promise is never resolved or rejected, it will leak memory, since the
47/// rust callbacks will never be freed.
48#[derive(Clone)]
49pub struct RawPromise {
50	p: stdweb::Reference,
51}
52impl RawPromise {
53	/// Wraps an existing promise.
54	/// 
55	/// This function does not check if the reference points to a valid promise object;
56	/// if it doesn't have a `then` method, methods on the wrapper will likely panic.
57	pub fn from_reference(p: stdweb::Reference) -> Self {
58		Self { p }
59	}
60	
61	/// Wraps an existing promise.
62	/// 
63	/// Panics if the value is not a reference.
64	/// 
65	/// This function does not check if the reference points to a valid promise object;
66	/// if it doesn't have a `then` method, methods on the wrapper will likely panic.
67	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	/// Gets the JS reference object to this promise.
72	pub fn js_obj(&self) -> &stdweb::Reference {
73		&self.p
74	}
75	
76	/// Creates a new promise, already resolved to a value.
77	/// 
78	/// Calls the JS `Promise.resolve` method.
79	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	/// Creates a new promise, already rejected
85	/// 
86	/// Calls the JS `Promise.reject` method.
87	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	/// Creates a new, already fulfilled promise from a `Result`.
93	/// 
94	/// Either resolved if the passed-in `Result` is `Ok`, or rejected if
95	/// it was `Err`.
96	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	/// Runs a function once the promise has resolved.
104	/// 
105	/// Instead of separate resolve and reject functions, the single callback is passed a
106	/// `Result` object. The callback returns a JS value that is interpreted as usual for a
107	/// promise return value.
108	/// 
109	/// Panics
110	/// ------
111	/// 
112	/// If this promise was created by `from_reference` with an invalid promise object
113	/// (no `then` method, `then` didn't return an object, etc).
114	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	/// Runs a function once the promise has resolved, returning Rust objects.
133	/// 
134	/// This is the main way to switch from JS promises to this crate's typed `Promise` type.
135	/// 
136	/// Panics
137	/// ------
138	/// 
139	/// If this promise was created by `from_reference` with an invalid promise object
140	/// (no `then` method, `then` didn't return an object, etc).
141	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	/// Creates a new promise that either resolves when all promises passed to it resolve or rejects when at least one
153	/// promise passed to it rejects.
154	/// 
155	/// Binding for JS's `Promise.all` function; see its documentation for semantics.
156	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	/// Creates a new promise that resolves when all promises passed to it have resolved or have been rejected.
167	/// 
168	/// Binding for JS's `Promsie.allSettled` function; see its documentation for semantics.
169	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	/// Creates a new promise that waits until the any of the promises passed to it have been resolved or rejected,
180	/// returning its result.
181	/// 
182	/// Binding for JS's `Promsie.race` function; see its documentation for semantics.
183	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
195/// Typed promises, working with Rust types.
196/// 
197/// This provides a higher level, Rust-like interface for working with Javascript promises.
198/// It allows passing Rust values through promises (even if they don't implement `stdweb::JsSerialize`).
199/// 
200/// Other than the basic functions for creating already-resolved promises, the usual way to create a `Promise`
201/// is to create a `RawPromise` object using `js!{}` with a JS API, then use `RawPromise::then_to_typed` to
202/// convert the raw JS results to a Rust type (or do something else with it).
203/// 
204/// If you need to pass the promise to a JS API, you can use `Promise::then_to_raw` to convert the rust objects
205/// to JS objects and get a `RawPromise`, which exposes a JS promise object via `js_obj`.
206/// 
207/// The result types must have only `'static` references, since promises may resolve arbitrairly late, or
208/// even not at all.
209/// 
210/// Note that there are a few limitations of this API due to how the internal passing of Rust objects works:
211/// * Chained methods like `then` consume the promise, preventing code from using the promise results multiple
212///   times. This is because the result values have to be moved out of the internal box, making them unavailable
213///   for other promises. This may be relaxed in the future for types that impement `Clone`.
214/// * For the above reason, and to avoid exposing internals that may result in other unsafe behavior, you cannot
215///   get the JS promsie object from this type. You can, however, use `then_to_raw` to get a `RawPromise`, which
216///   you can get the promise object of.
217/// * Like `RawPromise`, if a promise is never resolved, it will leak memory.
218pub 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	/// Runs a function once the promise has resolved.
225	/// 
226	/// The callback is passed a `Result`, which is `Ok` if the promise was resolved or `Err` if rejected.
227	/// It returns a `PromiseResult`, which can contain an immediate value to resolve to, another promise to run,
228	/// or an error to reject the promise with.
229	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						// Likely that previous promise threw an error and returned null.
235						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						// Likely that previous promise threw an error and returned null.
251						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	/// Creates a new promise, already resolved to a value
277	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	/// Creates a new promise, already rejected
286	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	/// Creates a new, already fulfilled promise from a `Result`.
295	/// 
296	/// Either resolved if the passed-in `Result` is `Ok`, or rejected if
297	/// it was `Err`.
298	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	/// Runs a function once the promise has resolved, returning JS objects.
307	/// 
308	/// Use this to convert a typed promise into a raw promise, that can be passed
309	/// to JS code.
310	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	/// Runs an operations on the resolve value if the promise resolves successfully.
323	/// 
324	/// Leaves rejected promises alone.
325	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	/// Runs an operation on the reject value if the promise is rejected.
330	/// 
331	/// Leaves resolved promises alone.
332	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	/// Runs an operation on the resolve value if the promise resolves successfully, possibly failing or returning another promise.
337	/// 
338	/// Leaves rejected promises alone, forwarding the error object.
339	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	/// Creates a promise that resolves when all of the passed-in promises have been resolved or rejected.
350	/// 
351	/// Resolves to a `Vec` of results for each of the promises. Never rejected.
352	/// 
353	/// This is currently the only promise combinator for typed `Promise`s, due to memory leak issues
354	/// with the other functions.
355	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 nothing is receiving the value from this promise, set up a callback to release the unused
400		// results to prevent leaking memory.
401		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
414/// Return type for `Promise.then`, which can be an immediate value to resolve to,
415/// another promise to run, or an immediate value to reject.
416/// 
417/// Similar in usage to `futures::Poll`.
418pub type PromiseResult<TOk, TErr> = Result<PromiseOk<TOk, TErr>, TErr>;
419
420/// A non-error result from a promise; can be either an immediate Ok value, or another promise to execute.
421/// 
422/// Similar in usage to `futures::Async`, though returns a promise to exeute instead of
423/// a simple `NotReady` value.
424pub 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
439/// Converts a `PromiseResult` to a JS value, boxing immediate values.
440fn 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	// Dumb panic handling.
453	// Don't want to just reject the promise, because a) that doesn't really match normal panic behavior,
454	// and b) could throw an error with an invalid box, breaking safety.
455	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}