@@ -8,11 +8,12 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture};
88
99use rustpython_vm:: builtins:: { PyFloatRef , PyStrRef , PyTypeRef } ;
1010use rustpython_vm:: exceptions:: PyBaseExceptionRef ;
11- use rustpython_vm:: function:: { Args , OptionalArg } ;
11+ use rustpython_vm:: function:: { Args , OptionalArg , OptionalOption } ;
1212use rustpython_vm:: pyobject:: {
1313 BorrowValue , IntoPyObject , PyCallable , PyClassImpl , PyObjectRef , PyRef , PyResult , PyValue ,
1414 StaticType , TryFromObject ,
1515} ;
16+ use rustpython_vm:: slots:: PyIter ;
1617use rustpython_vm:: types:: create_simple_type;
1718use 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 ) ]
374375pub struct PyPromise {
375- value : Promise ,
376+ value : PromiseKind ,
376377}
377378pub 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+
379388impl PyValue for PyPromise {
380389 fn class ( _vm : & VirtualMachine ) -> & PyTypeRef {
381390 Self :: static_type ( )
@@ -385,81 +394,208 @@ impl PyValue for PyPromise {
385394#[ pyimpl]
386395impl 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