Skip to main content

ferrijs_std/stream_web/utils/
promise.rs

1use 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
104// https://webidl.spec.whatwg.org/#dfn-perform-steps-once-promise-is-settled
105pub 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}