ion/object/
promise.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::future::Future;
8use std::ops::{Deref, DerefMut};
9use std::ptr::NonNull;
10
11use futures_executor::block_on;
12use mozjs::gc::HandleObject;
13use mozjs::glue::JS_GetPromiseResult;
14use mozjs::jsapi::{
15	AddPromiseReactions, CallOriginalPromiseReject, CallOriginalPromiseResolve, GetPromiseID, GetPromiseState,
16	IsPromiseObject, JSObject, NewPromiseObject, PromiseState, RejectPromise, ResolvePromise,
17};
18use mozjs::rooted;
19
20use crate::conversions::ToValue;
21use crate::flags::PropertyFlags;
22use crate::{Context, Error, Function, Local, Object, ResultExc, Value};
23
24/// Represents a [Promise] in the JavaScript Runtime.
25/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for more details.
26#[derive(Debug)]
27pub struct Promise<'p> {
28	promise: Local<'p, *mut JSObject>,
29}
30
31impl<'p> Promise<'p> {
32	/// Creates a new [Promise] which never resolves.
33	pub fn new(cx: &'p Context) -> Promise<'p> {
34		Promise {
35			promise: cx.root(unsafe { NewPromiseObject(cx.as_ptr(), HandleObject::null().into()) }),
36		}
37	}
38
39	/// Creates a new [Promise] with an executor.
40	/// The executor is a function that takes in two functions, `resolve` and `reject`.
41	/// `resolve` and `reject` can be called with a [Value] to resolve or reject the promise with the given value.
42	pub fn with_executor<F>(cx: &'p Context, executor: F) -> Option<Promise<'p>>
43	where
44		F: for<'cx> FnOnce(&'cx Context, Function<'cx>, Function<'cx>) -> crate::Result<()> + 'static,
45	{
46		unsafe {
47			let function = Function::from_closure_once(
48				cx,
49				c"executor",
50				Box::new(move |args| {
51					let cx = args.cx();
52					let resolve_obj = args.value(0).unwrap().to_object(cx).into_local();
53					let reject_obj = args.value(1).unwrap().to_object(cx).into_local();
54					let resolve = Function::from_object(cx, &resolve_obj).unwrap();
55					let reject = Function::from_object(cx, &reject_obj).unwrap();
56
57					match executor(cx, resolve, reject) {
58						Ok(()) => Ok(Value::undefined_handle()),
59						Err(e) => Err(e.into()),
60					}
61				}),
62				2,
63				PropertyFlags::empty(),
64			);
65			let executor = function.to_object(cx);
66			let promise = NewPromiseObject(cx.as_ptr(), executor.handle().into());
67
68			NonNull::new(promise).map(|promise| Promise { promise: cx.root(promise.as_ptr()) })
69		}
70	}
71
72	/// Creates a new [Promise] with a [Future].
73	/// The future is run to completion on the current thread and cannot interact with an asynchronous runtime.
74	///
75	/// The [Result] of the future determines if the promise is resolved or rejected.
76	pub fn block_on_future<F, Output, Error>(cx: &'p Context, future: F) -> Option<Promise<'p>>
77	where
78		F: Future<Output = Result<Output, Error>> + 'static,
79		Output: for<'cx> ToValue<'cx> + 'static,
80		Error: for<'cx> ToValue<'cx> + 'static,
81	{
82		Promise::with_executor(cx, move |cx, resolve, reject| {
83			let null = Object::null(cx);
84			block_on(async move {
85				match future.await {
86					Ok(output) => {
87						let value = output.as_value(cx);
88						if let Err(Some(error)) = resolve.call(cx, &null, &[value]) {
89							println!("{}", error.format(cx));
90						}
91					}
92					Err(error) => {
93						let value = error.as_value(cx);
94						if let Err(Some(error)) = reject.call(cx, &null, &[value]) {
95							println!("{}", error.format(cx));
96						}
97					}
98				}
99			});
100			Ok(())
101		})
102	}
103
104	/// Creates a new [Promise], that is resolved to the given value.
105	/// Similar to `Promise.resolve`
106	pub fn resolved(cx: &'p Context, value: &Value) -> Promise<'p> {
107		Promise {
108			promise: cx.root(unsafe { CallOriginalPromiseResolve(cx.as_ptr(), value.handle().into()) }),
109		}
110	}
111
112	/// Creates a new [Promise], that is rejected to the given value.
113	/// Similar to `Promise.reject`
114	pub fn rejected(cx: &'p Context, value: &Value) -> Promise<'p> {
115		Promise {
116			promise: cx.root(unsafe { CallOriginalPromiseReject(cx.as_ptr(), value.handle().into()) }),
117		}
118	}
119
120	/// Creates a [Promise] from an object.
121	pub fn from(object: Local<'p, *mut JSObject>) -> Option<Promise<'p>> {
122		Promise::is_promise(&object).then_some(Promise { promise: object })
123	}
124
125	/// Creates a [Promise] from aj object
126	///
127	/// ### Safety
128	/// Object must be a Promise.
129	pub unsafe fn from_unchecked(object: Local<'p, *mut JSObject>) -> Promise<'p> {
130		Promise { promise: object }
131	}
132
133	/// Returns the ID of the [Promise].
134	pub fn id(&self) -> u64 {
135		unsafe { GetPromiseID(self.handle().into()) }
136	}
137
138	/// Returns the state of the [Promise].
139	///
140	/// The state can be `Pending`, `Fulfilled` and `Rejected`.
141	pub fn state(&self) -> PromiseState {
142		unsafe { GetPromiseState(self.handle().into()) }
143	}
144
145	/// Returns the result of the [Promise].
146	pub fn result<'cx>(&self, cx: &'cx Context) -> Value<'cx> {
147		let mut value = Value::undefined(cx);
148		unsafe { JS_GetPromiseResult(self.handle().into(), value.handle_mut().into()) }
149		value
150	}
151
152	pub fn add_reactions<'cx, T, C>(&self, cx: &'cx Context, on_resolved: T, on_rejected: C) -> bool
153	where
154		T: for<'cx2> FnOnce(&'cx2 Context, &Value<'cx2>) -> ResultExc<Value<'cx2>> + 'static,
155		C: for<'cx2> FnOnce(&'cx2 Context, &Value<'cx2>) -> ResultExc<Value<'cx2>> + 'static,
156	{
157		let on_resolved = wrap_reaction(cx, on_resolved);
158		let on_rejected = wrap_reaction(cx, on_rejected);
159
160		unsafe {
161			AddPromiseReactions(
162				cx.as_ptr(),
163				self.handle().into(),
164				on_resolved.handle().into(),
165				on_rejected.handle().into(),
166			)
167		}
168	}
169
170	pub fn then<'cx, F>(&self, cx: &'cx Context, on_resolved: F) -> bool
171	where
172		F: for<'cx2> FnOnce(&'cx2 Context, &Value<'cx2>) -> ResultExc<Value<'cx2>> + 'static,
173	{
174		let on_resolved = wrap_reaction(cx, on_resolved);
175
176		unsafe {
177			AddPromiseReactions(
178				cx.as_ptr(),
179				self.handle().into(),
180				on_resolved.handle().into(),
181				HandleObject::null().into(),
182			)
183		}
184	}
185
186	pub fn catch<'cx, F>(&self, cx: &'cx Context, on_rejected: F) -> bool
187	where
188		F: for<'cx2> FnOnce(&'cx2 Context, &Value<'cx2>) -> ResultExc<Value<'cx2>> + 'static,
189	{
190		let on_rejected = wrap_reaction(cx, on_rejected);
191
192		unsafe {
193			AddPromiseReactions(
194				cx.as_ptr(),
195				self.handle().into(),
196				HandleObject::null().into(),
197				on_rejected.handle().into(),
198			)
199		}
200	}
201
202	/// Adds Reactions to the [Promise]
203	///
204	/// `on_resolved` is similar to calling `.then()` on a promise.
205	///
206	/// `on_rejected` is similar to calling `.catch()` on a promise.
207	pub fn add_reactions_native(
208		&self, cx: &'_ Context, on_resolved: Option<Function<'_>>, on_rejected: Option<Function<'_>>,
209	) -> bool {
210		let mut resolved = Object::null(cx);
211		let mut rejected = Object::null(cx);
212		if let Some(on_resolved) = on_resolved {
213			resolved.handle_mut().set(on_resolved.to_object(cx).handle().get());
214		}
215		if let Some(on_rejected) = on_rejected {
216			rejected.handle_mut().set(on_rejected.to_object(cx).handle().get());
217		}
218		unsafe {
219			AddPromiseReactions(
220				cx.as_ptr(),
221				self.handle().into(),
222				resolved.handle().into(),
223				rejected.handle().into(),
224			)
225		}
226	}
227
228	/// Resolves the [Promise] with the given [Value].
229	pub fn resolve(&self, cx: &Context, value: &Value) -> bool {
230		unsafe { ResolvePromise(cx.as_ptr(), self.handle().into(), value.handle().into()) }
231	}
232
233	/// Rejects the [Promise] with the given [Value].
234	pub fn reject(&self, cx: &Context, value: &Value) -> bool {
235		unsafe { RejectPromise(cx.as_ptr(), self.handle().into(), value.handle().into()) }
236	}
237
238	/// Rejects the [Promise] with the given [Error].
239	pub fn reject_with_error(&self, cx: &Context, error: &Error) -> bool {
240		self.reject(cx, &error.as_value(cx))
241	}
242
243	/// Checks if a [*mut] [JSObject] is a promise.
244	pub fn is_promise_raw(cx: &Context, object: *mut JSObject) -> bool {
245		rooted!(in(cx.as_ptr()) let object = object);
246		unsafe { IsPromiseObject(object.handle().into()) }
247	}
248
249	/// Checks if an object is a promise.
250	pub fn is_promise(object: &Local<*mut JSObject>) -> bool {
251		unsafe { IsPromiseObject(object.handle().into()) }
252	}
253}
254
255impl<'p> Deref for Promise<'p> {
256	type Target = Local<'p, *mut JSObject>;
257
258	fn deref(&self) -> &Self::Target {
259		&self.promise
260	}
261}
262
263impl DerefMut for Promise<'_> {
264	fn deref_mut(&mut self) -> &mut Self::Target {
265		&mut self.promise
266	}
267}
268
269fn wrap_reaction<'cx, F>(cx: &'cx Context, reaction: F) -> Object<'cx>
270where
271	F: for<'cx2> FnOnce(&'cx2 Context, &Value<'cx2>) -> ResultExc<Value<'cx2>> + 'static,
272{
273	Function::from_closure_once(
274		cx,
275		c"",
276		Box::new(move |args| {
277			let value = args.value(0).unwrap();
278			reaction(args.cx(), &value)
279		}),
280		1,
281		PropertyFlags::empty(),
282	)
283	.to_object(cx)
284}