1use 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#[derive(Debug)]
27pub struct Promise<'p> {
28 promise: Local<'p, *mut JSObject>,
29}
30
31impl<'p> Promise<'p> {
32 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 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 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 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 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 pub fn from(object: Local<'p, *mut JSObject>) -> Option<Promise<'p>> {
122 Promise::is_promise(&object).then_some(Promise { promise: object })
123 }
124
125 pub unsafe fn from_unchecked(object: Local<'p, *mut JSObject>) -> Promise<'p> {
130 Promise { promise: object }
131 }
132
133 pub fn id(&self) -> u64 {
135 unsafe { GetPromiseID(self.handle().into()) }
136 }
137
138 pub fn state(&self) -> PromiseState {
142 unsafe { GetPromiseState(self.handle().into()) }
143 }
144
145 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 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 pub fn resolve(&self, cx: &Context, value: &Value) -> bool {
230 unsafe { ResolvePromise(cx.as_ptr(), self.handle().into(), value.handle().into()) }
231 }
232
233 pub fn reject(&self, cx: &Context, value: &Value) -> bool {
235 unsafe { RejectPromise(cx.as_ptr(), self.handle().into(), value.handle().into()) }
236 }
237
238 pub fn reject_with_error(&self, cx: &Context, error: &Error) -> bool {
240 self.reject(cx, &error.as_value(cx))
241 }
242
243 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 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}