diff --git a/core/runtime.rs b/core/runtime.rs index fbe97fd93e..b0ab92f467 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -686,6 +686,42 @@ impl JsRuntime { scope.perform_microtask_checkpoint(); } + pub fn poll_value( + &mut self, + global: &v8::Global, + cx: &mut Context, + ) -> Poll, Error>> { + let state = self.poll_event_loop(cx, false); + + let mut scope = self.handle_scope(); + let local = v8::Local::::new(&mut scope, global); + + if let Ok(promise) = v8::Local::::try_from(local) { + match promise.state() { + v8::PromiseState::Pending => match state { + Poll::Ready(Ok(_)) => { + let msg = "Promise resolution is still pending but the event loop has already resolved."; + Poll::Ready(Err(generic_error(msg))) + } + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + }, + v8::PromiseState::Fulfilled => { + let value = promise.result(&mut scope); + let value_handle = v8::Global::new(&mut scope, value); + Poll::Ready(Ok(value_handle)) + } + v8::PromiseState::Rejected => { + let exception = promise.result(&mut scope); + Poll::Ready(exception_to_err_result(&mut scope, exception, false)) + } + } + } else { + let value_handle = v8::Global::new(&mut scope, local); + Poll::Ready(Ok(value_handle)) + } + } + /// Waits for the given value to resolve while polling the event loop. /// /// This future resolves when either the value is resolved or the event loop runs to @@ -694,38 +730,7 @@ impl JsRuntime { &mut self, global: v8::Global, ) -> Result, Error> { - poll_fn(|cx| { - let state = self.poll_event_loop(cx, false); - - let mut scope = self.handle_scope(); - let local = v8::Local::::new(&mut scope, &global); - - if let Ok(promise) = v8::Local::::try_from(local) { - match promise.state() { - v8::PromiseState::Pending => match state { - Poll::Ready(Ok(_)) => { - let msg = "Promise resolution is still pending but the event loop has already resolved."; - Poll::Ready(Err(generic_error(msg))) - }, - Poll::Ready(Err(e)) => Poll::Ready(Err(e)), - Poll::Pending => Poll::Pending, - }, - v8::PromiseState::Fulfilled => { - let value = promise.result(&mut scope); - let value_handle = v8::Global::new(&mut scope, value); - Poll::Ready(Ok(value_handle)) - } - v8::PromiseState::Rejected => { - let exception = promise.result(&mut scope); - Poll::Ready(exception_to_err_result(&mut scope, exception, false)) - } - } - } else { - let value_handle = v8::Global::new(&mut scope, local); - Poll::Ready(Ok(value_handle)) - } - }) - .await + poll_fn(|cx| self.poll_value(&global, cx)).await } /// Runs event loop to completion @@ -1859,6 +1864,51 @@ pub mod tests { } } + #[tokio::test] + async fn test_poll_value() { + run_in_task(|cx| { + let mut runtime = JsRuntime::new(Default::default()); + let value_global = runtime + .execute_script("a.js", "Promise.resolve(1 + 2)") + .unwrap(); + let v = runtime.poll_value(&value_global, cx); + { + let scope = &mut runtime.handle_scope(); + assert!( + matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 3) + ); + } + + let value_global = runtime + .execute_script( + "a.js", + "Promise.resolve(new Promise(resolve => resolve(2 + 2)))", + ) + .unwrap(); + let v = runtime.poll_value(&value_global, cx); + { + let scope = &mut runtime.handle_scope(); + assert!( + matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 4) + ); + } + + let value_global = runtime + .execute_script("a.js", "Promise.reject(new Error('fail'))") + .unwrap(); + let v = runtime.poll_value(&value_global, cx); + assert!( + matches!(v, Poll::Ready(Err(e)) if e.downcast_ref::().unwrap().message == "Uncaught Error: fail") + ); + + let value_global = runtime + .execute_script("a.js", "new Promise(resolve => {})") + .unwrap(); + let v = runtime.poll_value(&value_global, cx); + matches!(v, Poll::Ready(Err(e)) if e.to_string() == "Promise resolution is still pending but the event loop has already resolved."); + }); + } + #[tokio::test] async fn test_resolve_value() { let mut runtime = JsRuntime::new(Default::default());