ferrijs_std/stream_web/utils/
promise.rs1use std::{cell::Cell, rc::Rc};
2
3use crate::utils::primordials::Primordial;
4use rquickjs::{
5 atom::PredefinedAtom,
6 class::{Trace, Tracer},
7 function::Constructor,
8 prelude::{IntoArg, OnceFn, This},
9 promise::PromiseState,
10 Ctx, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, Value,
11};
12
13pub fn promise_rejected_with<'js>(
14 primordials: &PromisePrimordials<'js>,
15 value: Value<'js>,
16) -> Result<Promise<'js>> {
17 primordials
18 .promise_reject
19 .call((This(primordials.promise_constructor.clone()), value))
20}
21
22pub fn promise_rejected_catch<'js>(
23 ctx: &Ctx<'js>,
24 promise_primordials: &PromisePrimordials<'js>,
25) -> Result<Promise<'js>> {
26 promise_rejected_with(promise_primordials, ctx.catch())
27}
28
29pub fn promise_rejected_with_constructor<'js, T: From<Error>>(
30 constructor: &Constructor<'js>,
31 promise_primordials: &PromisePrimordials<'js>,
32 msg: &str,
33) -> std::result::Result<Promise<'js>, T> {
34 let e: Value = constructor.call((msg,))?;
35 Ok(promise_rejected_with(promise_primordials, e)?)
36}
37
38pub fn promise_resolved_with<'js>(
39 ctx: &Ctx<'js>,
40 primordials: &PromisePrimordials<'js>,
41 value: Result<Value<'js>>,
42) -> Result<Promise<'js>> {
43 match value {
44 Ok(value) => primordials
45 .promise_resolve
46 .call((This(primordials.promise_constructor.clone()), value)),
47 Err(Error::Exception) => primordials
48 .promise_reject
49 .call((This(primordials.promise_constructor.clone()), ctx.catch())),
50 Err(err) => Err(err),
51 }
52}
53
54#[derive(JsLifetime, Clone)]
55pub struct PromisePrimordials<'js> {
56 pub promise_constructor: Constructor<'js>,
57 pub promise_resolve: Function<'js>,
58 pub promise_reject: Function<'js>,
59 pub promise_all: Function<'js>,
60 pub promise_resolved_with_undefined: Promise<'js>,
61 pub promise_prototype_then: Function<'js>,
62}
63
64impl<'js> Trace<'js> for PromisePrimordials<'js> {
65 fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
66 self.promise_constructor.trace(tracer);
67 self.promise_resolve.trace(tracer);
68 self.promise_reject.trace(tracer);
69 self.promise_all.trace(tracer);
70 self.promise_resolved_with_undefined.trace(tracer);
71 self.promise_prototype_then.trace(tracer);
72 }
73}
74
75impl<'js> Primordial<'js> for PromisePrimordials<'js> {
76 fn new(ctx: &Ctx<'js>) -> Result<Self>
77 where
78 Self: Sized,
79 {
80 let promise_constructor: Constructor<'js> = ctx.globals().get(PredefinedAtom::Promise)?;
81 let promise_resolve: Function<'js> = promise_constructor.get("resolve")?;
82 let promise_reject: Function<'js> = promise_constructor.get("reject")?;
83 let promise_all: Function<'js> = promise_constructor.get("all")?;
84 let promise_prototype_then: Function<'js> = promise_constructor
85 .get::<_, Object>("prototype")?
86 .get("then")?;
87
88 let promise_resolved_with_undefined = promise_resolve.call((
89 This(promise_constructor.clone()),
90 Value::new_undefined(ctx.clone()),
91 ))?;
92
93 Ok(Self {
94 promise_constructor,
95 promise_resolve,
96 promise_reject,
97 promise_all,
98 promise_resolved_with_undefined,
99 promise_prototype_then,
100 })
101 }
102}
103
104pub fn upon_promise<'js, Input: FromJs<'js> + 'js, Output: IntoJs<'js> + 'js>(
106 ctx: Ctx<'js>,
107 promise: Promise<'js>,
108 then: impl FnOnce(Ctx<'js>, std::result::Result<Input, Value<'js>>) -> Result<Output> + 'js,
109) -> Result<Promise<'js>> {
110 let promise_then = PromisePrimordials::get(&ctx)?
111 .promise_prototype_then
112 .clone();
113 let then_cb = Rc::new(Cell::new(Some(then)));
114 let then_cb2 = then_cb.clone();
115 promise_then.call((
116 This(promise),
117 Function::new(
118 ctx.clone(),
119 OnceFn::new(move |ctx, input| {
120 then_cb
121 .take()
122 .expect("Promise.then should only call either resolve or reject")(
123 ctx,
124 Ok(input),
125 )
126 }),
127 ),
128 Function::new(
129 ctx,
130 OnceFn::new(move |ctx, e: Value<'js>| {
131 then_cb2
132 .take()
133 .expect("Promise.then should only call either resolve or reject")(
134 ctx, Err(e)
135 )
136 }),
137 ),
138 ))
139}
140
141pub fn upon_promise_fulfilment<'js, Input: FromJs<'js> + 'js, Output: IntoJs<'js> + 'js>(
142 ctx: Ctx<'js>,
143 promise: Promise<'js>,
144 then: impl FnOnce(Ctx<'js>, Input) -> Result<Output> + 'js,
145) -> Result<Promise<'js>> {
146 let promise_then = PromisePrimordials::get(&ctx)?
147 .promise_prototype_then
148 .clone();
149 promise_then.call((This(promise), Function::new(ctx.clone(), OnceFn::new(then))))
150}
151
152#[derive(Debug, JsLifetime, Clone)]
153pub struct ResolveablePromise<'js> {
154 pub promise: Promise<'js>,
155 resolve: Option<Function<'js>>,
156 reject: Option<Function<'js>>,
157}
158
159impl<'js> ResolveablePromise<'js> {
160 pub fn new(ctx: &Ctx<'js>) -> Result<Self> {
161 let (promise, resolve, reject) = Promise::new(ctx)?;
162 Ok(Self {
163 promise,
164 resolve: Some(resolve),
165 reject: Some(reject),
166 })
167 }
168
169 pub fn resolved_with_undefined(primordials: &PromisePrimordials<'js>) -> Self {
170 Self {
171 promise: primordials.promise_resolved_with_undefined.clone(),
172 resolve: None,
173 reject: None,
174 }
175 }
176
177 pub fn rejected_with(primordials: &PromisePrimordials<'js>, error: Value<'js>) -> Result<Self> {
178 Ok(Self {
179 promise: promise_rejected_with(primordials, error)?,
180 resolve: None,
181 reject: None,
182 })
183 }
184
185 pub fn rejected_with_constructor(
186 primordials: &PromisePrimordials<'js>,
187 constructor: &Constructor<'js>,
188 msg: &str,
189 ) -> Result<Self> {
190 Ok(Self {
191 promise: promise_rejected_with_constructor::<rquickjs::Error>(
192 constructor,
193 primordials,
194 msg,
195 )?,
196 resolve: None,
197 reject: None,
198 })
199 }
200
201 pub fn resolve(&self, value: impl IntoArg<'js>) -> Result<()> {
202 if let Some(resolve) = &self.resolve {
203 let () = resolve.call((value,))?;
204 }
205 Ok(())
206 }
207
208 pub fn resolve_undefined(&self) -> Result<()> {
209 if let Some(resolve) = &self.resolve {
210 let () = resolve.call((rquickjs::Undefined,))?;
211 }
212 Ok(())
213 }
214
215 pub fn reject(&self, value: impl IntoArg<'js>) -> Result<()> {
216 if let Some(reject) = &self.reject {
217 let () = reject.call((value,))?;
218 }
219 Ok(())
220 }
221
222 pub fn reject_with_constructor(&self, constructor: &Constructor<'js>, msg: &str) -> Result<()> {
223 if let Some(reject) = &self.reject {
224 let e: Value = constructor.call((msg,))?;
225 let () = reject.call((e,))?;
226 }
227 Ok(())
228 }
229
230 pub fn is_pending(&self) -> bool {
231 self.promise.state() == PromiseState::Pending
232 }
233
234 pub fn set_is_handled(&self) -> Result<()> {
235 self.promise.catch()?.call((
236 This(self.promise.clone()),
237 Function::new(self.promise.ctx().clone(), || {}),
238 ))
239 }
240}
241
242impl<'js> Trace<'js> for ResolveablePromise<'js> {
243 fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
244 self.promise.trace(tracer);
245 self.resolve.trace(tracer);
246 self.reject.trace(tracer);
247 }
248}
249
250pub fn with_promise_result<'js>(
251 ctx: &Ctx<'js>,
252 f: impl FnOnce() -> Result<Promise<'js>>,
253) -> Result<Promise<'js>> {
254 match f() {
255 Ok(value) => Ok(value),
256 Err(Error::Exception) => promise_rejected_catch(ctx, &*PromisePrimordials::get(ctx)?),
257 Err(err) => Err(err),
258 }
259}