Skip to content

Commit 5d171f3

Browse files
committed
Rework js promises a lot
1 parent cb9650e commit 5d171f3

2 files changed

Lines changed: 198 additions & 60 deletions

File tree

wasm/lib/src/convert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue {
137137
// the browser module might not be injected
138138
if vm.try_class("_js", "Promise").is_ok() {
139139
if let Some(py_prom) = py_obj.payload::<js_module::PyPromise>() {
140-
return py_prom.value().into();
140+
return py_prom.as_js(vm).into();
141141
}
142142
}
143143

wasm/lib/src/js_module.rs

Lines changed: 197 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture};
88

99
use rustpython_vm::builtins::{PyFloatRef, PyStrRef, PyTypeRef};
1010
use rustpython_vm::exceptions::PyBaseExceptionRef;
11-
use rustpython_vm::function::{Args, OptionalArg};
11+
use rustpython_vm::function::{Args, OptionalArg, OptionalOption};
1212
use rustpython_vm::pyobject::{
1313
BorrowValue, IntoPyObject, PyCallable, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue,
1414
StaticType, TryFromObject,
1515
};
16+
use rustpython_vm::slots::PyIter;
1617
use rustpython_vm::types::create_simple_type;
1718
use rustpython_vm::VirtualMachine;
1819

@@ -369,13 +370,21 @@ impl JsClosure {
369370
}
370371
}
371372

372-
#[pyclass(module = "browser", name = "Promise")]
373-
#[derive(Debug)]
373+
#[pyclass(module = "_js", name = "Promise")]
374+
#[derive(Debug, Clone)]
374375
pub struct PyPromise {
375-
value: Promise,
376+
value: PromiseKind,
376377
}
377378
pub type PyPromiseRef = PyRef<PyPromise>;
378379

380+
#[derive(Debug, Clone)]
381+
enum PromiseKind {
382+
Js(Promise),
383+
PyProm { then: PyObjectRef },
384+
PyResolved(PyObjectRef),
385+
PyRejected(PyBaseExceptionRef),
386+
}
387+
379388
impl PyValue for PyPromise {
380389
fn class(_vm: &VirtualMachine) -> &PyTypeRef {
381390
Self::static_type()
@@ -385,81 +394,208 @@ impl PyValue for PyPromise {
385394
#[pyimpl]
386395
impl PyPromise {
387396
pub fn new(value: Promise) -> PyPromise {
388-
PyPromise { value }
397+
PyPromise {
398+
value: PromiseKind::Js(value),
399+
}
389400
}
390401
pub fn from_future<F>(future: F) -> PyPromise
391402
where
392403
F: future::Future<Output = Result<JsValue, JsValue>> + 'static,
393404
{
394405
PyPromise::new(future_to_promise(future))
395406
}
396-
pub fn value(&self) -> Promise {
397-
self.value.clone()
407+
pub fn as_js(&self, vm: &VirtualMachine) -> Promise {
408+
match &self.value {
409+
PromiseKind::Js(prom) => prom.clone(),
410+
PromiseKind::PyProm { then } => Promise::new(&mut |js_resolve, js_reject| {
411+
let resolve = move |res: PyObjectRef, vm: &VirtualMachine| {
412+
let _ = js_resolve.call1(&JsValue::UNDEFINED, &convert::py_to_js(vm, res));
413+
};
414+
let reject = move |err: PyBaseExceptionRef, vm: &VirtualMachine| {
415+
let _ =
416+
js_reject.call1(&JsValue::UNDEFINED, &convert::py_err_to_js_err(vm, &err));
417+
};
418+
let _ = vm.invoke(
419+
then,
420+
(
421+
vm.ctx.new_function("resolve", resolve),
422+
vm.ctx.new_function("reject", reject),
423+
),
424+
);
425+
}),
426+
PromiseKind::PyResolved(obj) => Promise::resolve(&convert::py_to_js(vm, obj.clone())),
427+
PromiseKind::PyRejected(err) => Promise::reject(&convert::py_err_to_js_err(vm, err)),
428+
}
429+
}
430+
431+
fn cast(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
432+
let then = vm.get_attribute_opt(obj.clone(), "then")?;
433+
let value = if let Some(then) = then.filter(|obj| vm.is_callable(obj)) {
434+
PromiseKind::PyProm { then }
435+
} else {
436+
PromiseKind::PyResolved(obj)
437+
};
438+
Ok(Self { value })
439+
}
440+
441+
fn cast_result(res: PyResult, vm: &VirtualMachine) -> PyResult<Self> {
442+
match res {
443+
Ok(res) => Self::cast(res, vm),
444+
Err(e) => Ok(Self {
445+
value: PromiseKind::PyRejected(e),
446+
}),
447+
}
448+
}
449+
450+
#[pyclassmethod]
451+
fn resolve(cls: PyTypeRef, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
452+
Self::cast(obj, vm)?.into_ref_with_type(vm, cls)
453+
}
454+
455+
#[pyclassmethod]
456+
fn reject(
457+
cls: PyTypeRef,
458+
err: PyBaseExceptionRef,
459+
vm: &VirtualMachine,
460+
) -> PyResult<PyRef<Self>> {
461+
Self {
462+
value: PromiseKind::PyRejected(err),
463+
}
464+
.into_ref_with_type(vm, cls)
398465
}
399466

400467
#[pymethod]
401468
fn then(
402469
&self,
403-
on_fulfill: PyCallable,
404-
on_reject: OptionalArg<PyCallable>,
470+
on_fulfill: OptionalOption<PyCallable>,
471+
on_reject: OptionalOption<PyCallable>,
405472
vm: &VirtualMachine,
406-
) -> PyPromiseRef {
407-
let weak_vm = weak_vm(vm);
408-
let prom = JsFuture::from(self.value.clone());
409-
410-
let ret_future = async move {
411-
let stored_vm = &weak_vm
412-
.upgrade()
413-
.expect("that the vm is valid when the promise resolves");
414-
let res = prom.await;
415-
match res {
416-
Ok(val) => stored_vm.interp.enter(move |vm| {
417-
let args = if val.is_null() {
418-
vec![]
419-
} else {
420-
vec![convert::js_to_py(vm, val)]
421-
};
422-
let res = vm.invoke(&on_fulfill.into_object(), args);
423-
convert::pyresult_to_jsresult(vm, res)
424-
}),
425-
Err(err) => {
426-
if let OptionalArg::Present(on_reject) = on_reject {
427-
stored_vm.interp.enter(move |vm| {
428-
let err = convert::js_to_py(vm, err);
429-
let res = vm.invoke(&on_reject.into_object(), (err,));
430-
convert::pyresult_to_jsresult(vm, res)
431-
})
432-
} else {
433-
Err(err)
473+
) -> PyResult<PyPromise> {
474+
let (on_fulfill, on_reject) = (on_fulfill.flatten(), on_reject.flatten());
475+
if on_fulfill.is_none() && on_reject.is_none() {
476+
return Ok(self.clone());
477+
}
478+
match &self.value {
479+
PromiseKind::Js(prom) => {
480+
let weak_vm = weak_vm(vm);
481+
let prom = JsFuture::from(prom.clone());
482+
483+
let ret_future = async move {
484+
let stored_vm = &weak_vm
485+
.upgrade()
486+
.expect("that the vm is valid when the promise resolves");
487+
let res = prom.await;
488+
match res {
489+
Ok(val) => match on_fulfill {
490+
Some(on_fulfill) => stored_vm.interp.enter(move |vm| {
491+
let val = convert::js_to_py(vm, val);
492+
let res = on_fulfill.invoke((val,), vm);
493+
convert::pyresult_to_jsresult(vm, res)
494+
}),
495+
None => Ok(val),
496+
},
497+
Err(err) => match on_reject {
498+
Some(on_reject) => stored_vm.interp.enter(move |vm| {
499+
let err = convert::js_to_py(vm, err);
500+
let res = on_reject.invoke((err,), vm);
501+
convert::pyresult_to_jsresult(vm, res)
502+
}),
503+
None => Err(err),
504+
},
434505
}
435-
}
506+
};
507+
508+
Ok(PyPromise::from_future(ret_future))
436509
}
437-
};
510+
PromiseKind::PyProm { then } => {
511+
Self::cast_result(vm.invoke(then, (on_fulfill, on_reject)), vm)
512+
}
513+
PromiseKind::PyResolved(res) => match on_fulfill {
514+
Some(resolve) => Self::cast_result(resolve.invoke((res.clone(),), vm), vm),
515+
None => Ok(self.clone()),
516+
},
517+
PromiseKind::PyRejected(err) => match on_reject {
518+
Some(reject) => Self::cast_result(reject.invoke((err.clone(),), vm), vm),
519+
None => Ok(self.clone()),
520+
},
521+
}
522+
}
438523

439-
PyPromise::from_future(ret_future).into_ref(vm)
524+
#[pymethod]
525+
fn catch(
526+
&self,
527+
on_reject: OptionalOption<PyCallable>,
528+
vm: &VirtualMachine,
529+
) -> PyResult<PyPromise> {
530+
self.then(OptionalArg::Present(None), on_reject, vm)
440531
}
441532

533+
#[pymethod(name = "__await__")]
534+
fn r#await(zelf: PyRef<Self>) -> AwaitPromise {
535+
AwaitPromise {
536+
obj: Some(zelf.into_object()).into(),
537+
}
538+
}
539+
}
540+
541+
#[pyclass(module = "_js", name = "AwaitPromise")]
542+
struct AwaitPromise {
543+
obj: cell::Cell<Option<PyObjectRef>>,
544+
}
545+
546+
impl fmt::Debug for AwaitPromise {
547+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
548+
f.debug_struct("AwaitPromise").finish()
549+
}
550+
}
551+
552+
impl PyValue for AwaitPromise {
553+
fn class(_vm: &VirtualMachine) -> &PyTypeRef {
554+
Self::static_type()
555+
}
556+
}
557+
558+
#[pyimpl(with(PyIter))]
559+
impl AwaitPromise {
442560
#[pymethod]
443-
fn catch(&self, on_reject: PyCallable, vm: &VirtualMachine) -> PyPromiseRef {
444-
let weak_vm = weak_vm(vm);
445-
let prom = JsFuture::from(self.value.clone());
446-
447-
let ret_future = async move {
448-
let err = match prom.await {
449-
Ok(x) => return Ok(x),
450-
Err(e) => e,
451-
};
452-
let stored_vm = weak_vm
453-
.upgrade()
454-
.expect("that the vm is valid when the promise resolves");
455-
stored_vm.interp.enter(move |vm| {
456-
let err = convert::js_to_py(vm, err);
457-
let res = vm.invoke(&on_reject.into_object(), (err,));
458-
convert::pyresult_to_jsresult(vm, res)
459-
})
460-
};
561+
fn send(&self, val: Option<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
562+
match self.obj.take() {
563+
Some(prom) => {
564+
if val.is_some() {
565+
Err(vm
566+
.new_type_error("can't send non-None value to an awaitpromise".to_owned()))
567+
} else {
568+
Ok(prom)
569+
}
570+
}
571+
None => Err(rustpython_vm::iterator::stop_iter_with_value(
572+
vm.unwrap_or_none(val),
573+
vm,
574+
)),
575+
}
576+
}
461577

462-
PyPromise::from_future(ret_future).into_ref(vm)
578+
#[pymethod]
579+
fn throw(
580+
&self,
581+
exc_type: PyObjectRef,
582+
exc_val: OptionalArg,
583+
exc_tb: OptionalArg,
584+
vm: &VirtualMachine,
585+
) -> PyResult {
586+
let err = rustpython_vm::exceptions::normalize(
587+
exc_type,
588+
exc_val.unwrap_or_none(vm),
589+
exc_tb.unwrap_or_none(vm),
590+
vm,
591+
)?;
592+
Err(err)
593+
}
594+
}
595+
596+
impl PyIter for AwaitPromise {
597+
fn next(zelf: &PyRef<Self>, vm: &VirtualMachine) -> PyResult {
598+
zelf.send(None, vm)
463599
}
464600
}
465601

@@ -478,6 +614,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
478614
"value" => ctx.new_readonly_getset("value", |exc: PyBaseExceptionRef| exc.get_arg(0)),
479615
});
480616

617+
AwaitPromise::make_class(ctx);
618+
481619
py_module!(vm, "_js", {
482620
"JSError" => js_error,
483621
"JSValue" => PyJsValue::make_class(ctx),

0 commit comments

Comments
 (0)