diff --git a/lib/web_ui/lib/src/engine/app_bootstrap.dart b/lib/web_ui/lib/src/engine/app_bootstrap.dart
index f3d5a70cc3424..dc0bf098f4a05 100644
--- a/lib/web_ui/lib/src/engine/app_bootstrap.dart
+++ b/lib/web_ui/lib/src/engine/app_bootstrap.dart
@@ -2,11 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:js/js_util.dart' show allowInterop;
-
 import 'configuration.dart';
 import 'js_interop/js_loader.dart';
-import 'js_interop/js_promise.dart';
 
 /// The type of a function that initializes an engine (in Dart).
 typedef InitEngineFn = Future<void> Function([JsFlutterConfiguration? params]);
@@ -40,33 +37,26 @@ class AppBootstrap {
       // This is a convenience method that lets the programmer call "autoStart"
       // from JavaScript immediately after the main.dart.js has loaded.
       // Returns a promise that resolves to the Flutter app that was started.
-      autoStart: allowInterop(() => futureToPromise(() async {
+      autoStart: () async {
         await autoStart();
         // Return the App that was just started
         return _prepareFlutterApp();
-      }())),
+      },
       // Calls [_initEngine], and returns a JS Promise that resolves to an
       // app runner object.
-      initializeEngine: allowInterop(([JsFlutterConfiguration? configuration]) => futureToPromise(() async {
+      initializeEngine: ([JsFlutterConfiguration? configuration]) async {
         await _initializeEngine(configuration);
         return _prepareAppRunner();
-      }()))
+      }
     );
   }
 
   /// Creates an appRunner that runs our encapsulated runApp function.
   FlutterAppRunner _prepareAppRunner() {
-    return FlutterAppRunner(runApp: allowInterop(([RunAppFnParameters? params]) {
-      // `params` coming from JS may be used to configure the run app method.
-      return Promise<FlutterApp>(allowInterop((
-        PromiseResolver<FlutterApp> resolve,
-        PromiseRejecter _,
-      ) async {
-        await _runApp();
-        // Return the App that was just started
-        resolve.resolve(_prepareFlutterApp());
-      }));
-    }));
+    return FlutterAppRunner(runApp: ([RunAppFnParameters? params]) async {
+      await _runApp();
+      return _prepareFlutterApp();
+    });
   }
 
   /// Represents the App that was just started, and its JS API.
diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart
index 13563d0b52902..74a06d6cd4d6d 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart
@@ -142,7 +142,7 @@ Future<ByteBuffer> readVideoFramePixelsUnmodified(VideoFrame videoFrame) async {
   // In dart2wasm, Uint8List is not the same as a JS Uint8Array. So we
   // explicitly construct the JS object here.
   final JSUint8Array destination = createUint8ArrayFromLength(size);
-  final JsPromise copyPromise = videoFrame.copyTo(destination);
+  final JSPromise copyPromise = videoFrame.copyTo(destination);
   await promiseToFuture<void>(copyPromise);
 
   // In dart2wasm, `toDart` incurs a copy here. On JS backends, this is a
diff --git a/lib/web_ui/lib/src/engine/js_interop/js_loader.dart b/lib/web_ui/lib/src/engine/js_interop/js_loader.dart
index 2e65f2aeb767c..283f3e1655fa0 100644
--- a/lib/web_ui/lib/src/engine/js_interop/js_loader.dart
+++ b/lib/web_ui/lib/src/engine/js_interop/js_loader.dart
@@ -8,9 +8,7 @@ library js_loader;
 import 'dart:js_interop';
 
 import 'package:js/js_util.dart' as js_util;
-
-import '../configuration.dart';
-import 'js_promise.dart';
+import 'package:ui/src/engine.dart';
 
 @JS()
 @staticInterop
@@ -34,6 +32,17 @@ extension FlutterLoaderExtension on FlutterLoader {
   bool get isAutoStart => !js_util.hasProperty(this, 'didCreateEngineInitializer');
 }
 
+/// Typedef for the function that initializes the flutter engine.
+/// ///
+/// [JsFlutterConfiguration] comes from `../configuration.dart`. It is the same
+/// object that can be used to configure flutter "inline", through the
+/// (to be deprecated) `window.flutterConfiguration` object.
+typedef InitializeEngineFn = Future<FlutterAppRunner> Function([JsFlutterConfiguration?]);
+
+/// Typedef for the `autoStart` function that can be called straight from an engine initializer instance.
+/// (Similar to [RunAppFn], but taking no specific "runApp" parameters).
+typedef ImmediateRunAppFn = Future<FlutterApp> Function();
+
 // FlutterEngineInitializer
 
 /// An object that allows the user to initialize the Engine of a Flutter App.
@@ -44,23 +53,19 @@ extension FlutterLoaderExtension on FlutterLoader {
 @anonymous
 @staticInterop
 abstract class FlutterEngineInitializer{
-  external factory FlutterEngineInitializer({
+  factory FlutterEngineInitializer({
     required InitializeEngineFn initializeEngine,
     required ImmediateRunAppFn autoStart,
+  }) => FlutterEngineInitializer._(
+      initializeEngine: (() => futureToPromise(initializeEngine())).toJS,
+      autoStart: (() => futureToPromise(autoStart())).toJS,
+    );
+  external factory FlutterEngineInitializer._({
+    required JSFunction initializeEngine,
+    required JSFunction autoStart,
   });
 }
 
-/// Typedef for the function that initializes the flutter engine.
-///
-/// [JsFlutterConfiguration] comes from `../configuration.dart`. It is the same
-/// object that can be used to configure flutter "inline", through the
-/// (to be deprecated) `window.flutterConfiguration` object.
-typedef InitializeEngineFn = Promise<FlutterAppRunner> Function([JsFlutterConfiguration?]);
-
-/// Typedef for the `autoStart` function that can be called straight from an engine initializer instance.
-/// (Similar to [RunAppFn], but taking no specific "runApp" parameters).
-typedef ImmediateRunAppFn = Promise<FlutterApp> Function();
-
 // FlutterAppRunner
 
 /// A class that exposes a function that runs the Flutter app,
@@ -68,10 +73,14 @@ typedef ImmediateRunAppFn = Promise<FlutterApp> Function();
 @JS()
 @anonymous
 @staticInterop
-abstract class FlutterAppRunner {
+abstract class FlutterAppRunner extends JSObject {
+  factory FlutterAppRunner({required RunAppFn runApp,}) => FlutterAppRunner._(
+    runApp: ((RunAppFnParameters args) => futureToPromise(runApp(args))).toJS
+  );
+
   /// Runs a flutter app
-  external factory FlutterAppRunner({
-    required RunAppFn runApp, // Returns an App
+  external factory FlutterAppRunner._({
+    required JSFunction runApp, // Returns an App
   });
 }
 
@@ -81,10 +90,11 @@ abstract class FlutterAppRunner {
 @anonymous
 @staticInterop
 abstract class RunAppFnParameters {
+  external factory RunAppFnParameters();
 }
 
 /// Typedef for the function that runs the flutter app main entrypoint.
-typedef RunAppFn = Promise<FlutterApp> Function([RunAppFnParameters?]);
+typedef RunAppFn = Future<FlutterApp> Function([RunAppFnParameters?]);
 
 // FlutterApp
 
@@ -92,7 +102,7 @@ typedef RunAppFn = Promise<FlutterApp> Function([RunAppFnParameters?]);
 @JS()
 @anonymous
 @staticInterop
-abstract class FlutterApp {
+abstract class FlutterApp extends JSObject {
   /// Cleans a Flutter app
   external factory FlutterApp();
 }
diff --git a/lib/web_ui/lib/src/engine/js_interop/js_promise.dart b/lib/web_ui/lib/src/engine/js_interop/js_promise.dart
index 5f7e61dd3217e..6ad06b21566b4 100644
--- a/lib/web_ui/lib/src/engine/js_interop/js_promise.dart
+++ b/lib/web_ui/lib/src/engine/js_interop/js_promise.dart
@@ -11,40 +11,27 @@ import 'package:js/js_util.dart' as js_util;
 
 import '../util.dart';
 
-@JS()
-@staticInterop
-class PromiseResolver<T extends Object?> {}
-
-extension PromiseResolverExtension<T extends Object?> on PromiseResolver<T> {
-  void resolve(T result) => js_util.callMethod(this, 'call', <Object>[this, if (result != null) result]);
-}
-
-@JS()
-@staticInterop
-class PromiseRejecter {}
-
-extension PromiseRejecterExtension on PromiseRejecter {
-  void reject(Object? error) => js_util.callMethod(this, 'call', <Object>[this, if (error != null) error]);
+extension CallExtension on JSFunction {
+  external void call(JSAny? this_, JSAny? object);
 }
 
-/// Type-safe JS Promises
 @JS('Promise')
-@staticInterop
-abstract class Promise<T extends Object?> {
-  /// A constructor for a JS promise
-  external factory Promise(PromiseExecutor<T> executor);
-}
+external JSAny get _promiseConstructor;
+
+JSPromise createPromise(JSFunction executor) =>
+  js_util.callConstructor(
+    _promiseConstructor,
+    <Object>[executor],
+  );
 
-/// The type of function that is used to create a Promise<T>
-typedef PromiseExecutor<T extends Object?> = void Function(PromiseResolver<T> resolve, PromiseRejecter reject);
 
-Promise<T> futureToPromise<T extends Object>(Future<T> future) {
-  return Promise<T>(js_util.allowInterop((PromiseResolver<T> resolver, PromiseRejecter rejecter) {
+JSPromise futureToPromise<T extends JSAny>(Future<T> future) {
+  return createPromise((JSFunction resolver, JSFunction rejecter) {
     future.then(
-      (T value) => resolver.resolve(value),
+      (T value) => resolver.call(null, value),
       onError: (Object? error) {
         printWarning('Rejecting promise with error: $error');
-        rejecter.reject(error);
+        rejecter.call(null, null);
       });
-  }));
+  }.toJS);
 }
diff --git a/lib/web_ui/lib/src/engine/safe_browser_api.dart b/lib/web_ui/lib/src/engine/safe_browser_api.dart
index a8359793fe53e..73ba4775ac169 100644
--- a/lib/web_ui/lib/src/engine/safe_browser_api.dart
+++ b/lib/web_ui/lib/src/engine/safe_browser_api.dart
@@ -196,20 +196,6 @@ bool get _defaultBrowserSupportsImageDecoder =>
 // enable it explicitly.
 bool get _isBrowserImageDecoderStable => browserEngine == BrowserEngine.blink;
 
-/// The signature of the function passed to the constructor of JavaScript `Promise`.
-typedef JsPromiseCallback = void Function(void Function(Object? value) resolve, void Function(Object? error) reject);
-
-/// Corresponds to JavaScript's `Promise`.
-///
-/// This type doesn't need any members. Instead, it should be first converted
-/// to Dart's [Future] using [promiseToFuture] then interacted with through the
-/// [Future] API.
-@JS('window.Promise')
-@staticInterop
-class JsPromise {
-  external factory JsPromise(JsPromiseCallback callback);
-}
-
 /// Corresponds to the browser's `ImageDecoder` type.
 ///
 /// See also:
@@ -228,7 +214,7 @@ extension ImageDecoderExtension on ImageDecoder {
   external JSBoolean get _complete;
   bool get complete => _complete.toDart;
 
-  external JsPromise decode(DecodeOptions options);
+  external JSPromise decode(DecodeOptions options);
   external JSVoid close();
 }
 
@@ -302,8 +288,8 @@ extension VideoFrameExtension on VideoFrame {
   double allocationSize() => _allocationSize().toDartDouble;
 
   @JS('copyTo')
-  external JsPromise _copyTo(JSAny destination);
-  JsPromise copyTo(Object destination) => _copyTo(destination.toJSAnyShallow);
+  external JSPromise _copyTo(JSAny destination);
+  JSPromise copyTo(Object destination) => _copyTo(destination.toJSAnyShallow);
 
   @JS('format')
   external JSString? get _format;
@@ -344,7 +330,7 @@ extension VideoFrameExtension on VideoFrame {
 class ImageTrackList {}
 
 extension ImageTrackListExtension on ImageTrackList {
-  external JsPromise get ready;
+  external JSPromise get ready;
   external ImageTrack? get selectedTrack;
 }
 
diff --git a/lib/web_ui/test/engine/app_bootstrap_test.dart b/lib/web_ui/test/engine/app_bootstrap_test.dart
index 609c569883bb6..14af5490dc9e0 100644
--- a/lib/web_ui/test/engine/app_bootstrap_test.dart
+++ b/lib/web_ui/test/engine/app_bootstrap_test.dart
@@ -88,9 +88,17 @@ void testMain() {
 
     final FlutterEngineInitializer engineInitializer = bootstrap.prepareEngineInitializer();
 
-    final Object appInitializer = await promiseToFuture<Object>(callMethod<Object>(engineInitializer, 'initializeEngine', <Object?>[]));
-    final Object maybeApp = await promiseToFuture<Object>(callMethod<Object>(appInitializer, 'runApp', <Object?>[]));
-
+    final Object appInitializer = await promiseToFuture<Object>(callMethod<Object>(
+      engineInitializer,
+      'initializeEngine',
+      <Object?>[]
+    ));
+    expect(appInitializer, isA<FlutterAppRunner>());
+    final Object maybeApp = await promiseToFuture<Object>(callMethod<Object>(
+      appInitializer,
+      'runApp',
+      <Object?>[RunAppFnParameters()]
+    ));
     expect(maybeApp, isA<FlutterApp>());
     expect(initCalled, 1, reason: 'initEngine should have been called.');
     expect(runCalled, 2, reason: 'runApp should have been called.');
diff --git a/lib/web_ui/test/engine/window_test.dart b/lib/web_ui/test/engine/window_test.dart
index 85f40e1662c3a..df39afb17adde 100644
--- a/lib/web_ui/test/engine/window_test.dart
+++ b/lib/web_ui/test/engine/window_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:js_interop';
 import 'dart:js_util' as js_util;
 import 'dart:typed_data';
 
@@ -331,19 +332,18 @@ Future<void> testMain() async {
     // The `orientation` property cannot be overridden, so this test overrides the entire `screen`.
     js_util.setProperty(domWindow, 'screen', js_util.jsify(<Object?, Object?>{
       'orientation': <Object?, Object?>{
-        'lock': allowInterop((String lockType) {
+        'lock': (String lockType) {
           lockCalls.add(lockType);
-          return Promise<Object?>(allowInterop((PromiseResolver<Object?> resolve, PromiseRejecter reject) {
-            if (!simulateError) {
-              resolve.resolve(null);
-            } else {
-              reject.reject('Simulating error');
+          return futureToPromise(() async {
+            if (simulateError) {
+              throw Error();
             }
-          }));
-        }),
-        'unlock': allowInterop(() {
+            return 0.toJS;
+          }());
+        }.toJS,
+        'unlock': () {
           unlockCount += 1;
-        }),
+        }.toJS,
       },
     }));