Skip to content

Commit ba1c5d7

Browse files
committed
Add a Promise class to the browser module
1 parent d6242ac commit ba1c5d7

2 files changed

Lines changed: 139 additions & 45 deletions

File tree

wasm/demo/snippets/fetch.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ def fetch_handler(res):
55

66
fetch(
77
"https://httpbin.org/get",
8-
fetch_handler,
9-
lambda err: print(f"error: {err}"),
108
response_format="json",
11-
headers={"X-Header-Thing": "rustpython is neat!"},
12-
)
9+
headers={
10+
"X-Header-Thing": "rustpython is neat!"
11+
},
12+
).then(fetch_handler, lambda err: print(f"error: {err}"))

wasm/lib/src/browser_module.rs

Lines changed: 135 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use crate::{convert, vm_class::AccessibleVM, wasm_builtins::window};
2-
use futures::{future, Future};
2+
use futures::Future;
33
use js_sys::Promise;
44
use rustpython_vm::obj::{objint, objstr};
5-
use rustpython_vm::pyobject::{PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol};
6-
use rustpython_vm::VirtualMachine;
5+
use rustpython_vm::pyobject::{
6+
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
7+
};
8+
use rustpython_vm::{import::import, VirtualMachine};
9+
use std::path::PathBuf;
710
use wasm_bindgen::{prelude::*, JsCast};
811
use wasm_bindgen_futures::{future_to_promise, JsFuture};
912

@@ -32,15 +35,10 @@ impl FetchResponseFormat {
3235
}
3336

3437
fn browser_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
35-
arg_check!(
36-
vm,
37-
args,
38-
required = [
39-
(url, Some(vm.ctx.str_type())),
40-
(handler, Some(vm.ctx.function_type()))
41-
],
42-
optional = [(reject_handler, Some(vm.ctx.function_type()))]
43-
);
38+
arg_check!(vm, args, required = [(url, Some(vm.ctx.str_type()))]);
39+
40+
let promise_type = import_promise_type(vm)?;
41+
4442
let response_format =
4543
args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?;
4644
let method = args.get_optional_kwarg_with_type("method", vm.ctx.str_type(), vm)?;
@@ -87,42 +85,16 @@ fn browser_fetch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
8785
let window = window();
8886
let request_prom = window.fetch_with_request(&request);
8987

90-
let handler = handler.clone();
91-
let reject_handler = reject_handler.cloned();
92-
93-
let acc_vm = AccessibleVM::from_vm(vm);
94-
9588
let future = JsFuture::from(request_prom)
9689
.and_then(move |val| {
9790
let response = val
9891
.dyn_into::<web_sys::Response>()
9992
.expect("val to be of type Response");
10093
response_format.get_response(&response)
10194
})
102-
.and_then(JsFuture::from)
103-
.then(move |val| {
104-
let vm = &mut acc_vm
105-
.upgrade()
106-
.expect("that the VM *not* be destroyed while promise is being resolved");
107-
match val {
108-
Ok(val) => {
109-
let val = convert::js_to_py(vm, val);
110-
let args = PyFuncArgs::new(vec![val], vec![]);
111-
let _ = vm.invoke(handler, args);
112-
}
113-
Err(val) => {
114-
if let Some(reject_handler) = reject_handler {
115-
let val = convert::js_to_py(vm, val);
116-
let args = PyFuncArgs::new(vec![val], vec![]);
117-
let _ = vm.invoke(reject_handler, args);
118-
}
119-
}
120-
}
121-
future::ok(JsValue::UNDEFINED)
122-
});
123-
future_to_promise(future);
95+
.and_then(JsFuture::from);
12496

125-
Ok(vm.get_none())
97+
Ok(PyPromise::new(promise_type, future_to_promise(future)))
12698
}
12799

128100
fn browser_request_animation_frame(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
@@ -167,7 +139,7 @@ fn browser_request_animation_frame(vm: &mut VirtualMachine, args: PyFuncArgs) ->
167139
fn browser_cancel_animation_frame(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
168140
arg_check!(vm, args, required = [(id, Some(vm.ctx.int_type()))]);
169141

170-
// fine because
142+
// questionable, but it's probably fine
171143
let id = objint::get_value(id)
172144
.to_string()
173145
.parse()
@@ -180,13 +152,135 @@ fn browser_cancel_animation_frame(vm: &mut VirtualMachine, args: PyFuncArgs) ->
180152
Ok(vm.get_none())
181153
}
182154

155+
pub struct PyPromise {
156+
value: Promise,
157+
}
158+
159+
impl PyPromise {
160+
pub fn new(promise_type: PyObjectRef, value: Promise) -> PyObjectRef {
161+
PyObject::new(
162+
PyObjectPayload::AnyRustValue {
163+
value: Box::new(PyPromise { value }),
164+
},
165+
promise_type,
166+
)
167+
}
168+
}
169+
170+
fn get_value(obj: &PyObjectRef) -> Promise {
171+
if let PyObjectPayload::AnyRustValue { value } = &obj.payload {
172+
if let Some(promise) = value.downcast_ref::<PyPromise>() {
173+
return promise.value.clone();
174+
}
175+
}
176+
panic!("Inner error getting promise")
177+
}
178+
179+
fn import_promise_type(vm: &mut VirtualMachine) -> PyResult {
180+
import(
181+
vm,
182+
PathBuf::default(),
183+
BROWSER_NAME,
184+
&Some("Promise".into()),
185+
)
186+
}
187+
188+
fn promise_then(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
189+
let promise_type = import_promise_type(vm)?;
190+
arg_check!(
191+
vm,
192+
args,
193+
required = [
194+
(zelf, Some(promise_type.clone())),
195+
(on_fulfill, Some(vm.ctx.function_type()))
196+
],
197+
optional = [(on_reject, Some(vm.ctx.function_type()))]
198+
);
199+
200+
let on_fulfill = on_fulfill.clone();
201+
let on_reject = on_reject.cloned();
202+
203+
let acc_vm = AccessibleVM::from_vm(vm);
204+
205+
let promise = get_value(zelf);
206+
207+
let ret_future = JsFuture::from(promise).then(move |res| {
208+
let vm = &mut acc_vm
209+
.upgrade()
210+
.expect("that the vm is valid when the promise resolves");
211+
let ret = match res {
212+
Ok(val) => {
213+
let val = convert::js_to_py(vm, val);
214+
vm.invoke(on_fulfill, PyFuncArgs::new(vec![val], vec![]))
215+
}
216+
Err(err) => {
217+
if let Some(on_reject) = on_reject {
218+
let err = convert::js_to_py(vm, err);
219+
vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![]))
220+
} else {
221+
return Err(err);
222+
}
223+
}
224+
};
225+
ret.map(|val| convert::py_to_js(vm, val))
226+
.map_err(|err| convert::py_to_js(vm, err))
227+
});
228+
229+
let ret_promise = future_to_promise(ret_future);
230+
231+
Ok(PyPromise::new(promise_type, ret_promise))
232+
}
233+
234+
fn promise_catch(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
235+
let promise_type = import_promise_type(vm)?;
236+
arg_check!(
237+
vm,
238+
args,
239+
required = [
240+
(zelf, Some(promise_type.clone())),
241+
(on_reject, Some(vm.ctx.function_type()))
242+
]
243+
);
244+
245+
let on_reject = on_reject.clone();
246+
247+
let acc_vm = AccessibleVM::from_vm(vm);
248+
249+
let promise = get_value(zelf);
250+
251+
let ret_future = JsFuture::from(promise).then(move |res| match res {
252+
Ok(val) => Ok(val),
253+
Err(err) => {
254+
let vm = &mut acc_vm
255+
.upgrade()
256+
.expect("that the vm is valid when the promise resolves");
257+
let err = convert::js_to_py(vm, err);
258+
vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![]))
259+
.map(|val| convert::py_to_js(vm, val))
260+
.map_err(|err| convert::py_to_js(vm, err))
261+
}
262+
});
263+
264+
let ret_promise = future_to_promise(ret_future);
265+
266+
Ok(PyPromise::new(promise_type, ret_promise))
267+
}
268+
183269
const BROWSER_NAME: &str = "browser";
184270

185271
pub fn mk_module(ctx: &PyContext) -> PyObjectRef {
272+
let promise = {
273+
let promise = ctx.new_class("Promise", ctx.object());
274+
ctx.set_attr(&promise, "then", ctx.new_rustfunc(promise_then));
275+
ctx.set_attr(&promise, "catch", ctx.new_rustfunc(promise_catch));
276+
promise
277+
};
278+
186279
py_module!(ctx, BROWSER_NAME, {
187280
"fetch" => ctx.new_rustfunc(browser_fetch),
188281
"request_animation_frame" => ctx.new_rustfunc(browser_request_animation_frame),
189282
"cancel_animation_frame" => ctx.new_rustfunc(browser_cancel_animation_frame),
283+
"Promise" => promise,
190284
})
191285
}
192286

0 commit comments

Comments
 (0)