diff --git a/Libraries/Core/Timers/queueMicrotask.js b/Libraries/Core/Timers/queueMicrotask.js new file mode 100644 index 00000000000000..a131696dcf64af --- /dev/null +++ b/Libraries/Core/Timers/queueMicrotask.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow + */ + +'use strict'; + +let resolvedPromise; + +/** + * Polyfill for the microtask queuening API defined by WHATWG HTMP spec. + * https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-queuemicrotask + * + * The method must queue a microtask to invoke @param {function} callback, and + * if the callback throws an exception, report the exception. + */ +export default function queueMicrotask(callback: Function) { + if (arguments.length < 1) { + throw new TypeError( + 'queueMicrotask must be called with at least one argument (a function to call)', + ); + } + if (typeof callback !== 'function') { + throw new TypeError('The argument to queueMicrotask must be a function.'); + } + + // Try to reuse a lazily allocated resolved promise from closure. + (resolvedPromise || (resolvedPromise = Promise.resolve())) + .then(callback) + .catch(error => + // Report the exception until the next tick. + setTimeout(() => { + throw error; + }, 0), + ); +} diff --git a/Libraries/Core/setUpTimers.js b/Libraries/Core/setUpTimers.js index ffca65d3798be6..8629872a3963c1 100644 --- a/Libraries/Core/setUpTimers.js +++ b/Libraries/Core/setUpTimers.js @@ -10,10 +10,21 @@ 'use strict'; +const {polyfillGlobal} = require('../Utilities/PolyfillFunctions'); + +if (__DEV__) { + if (typeof global.Promise !== 'function') { + console.error('Promise should exist before setting up timers.'); + } +} + +// Currently, Hermes `Promise` is implemented via Internal Bytecode. +const hasHermesPromiseQueuedToJSVM = + global?.HermesInternal?.hasPromise?.() && + global?.HermesInternal?.useEngineQueue?.(); + // In bridgeless mode, timers are host functions installed from cpp. if (!global.RN$Bridgeless) { - const {polyfillGlobal} = require('../Utilities/PolyfillFunctions'); - /** * Set up timers. * You can use this module directly, or just require InitializeCore. @@ -42,3 +53,18 @@ if (!global.RN$Bridgeless) { () => require('./Timers/JSTimers').clearReactNativeMicrotask, ); } + +/** + * Set up the microtask queueing API, which is required to use the same + * microtask queue as the Promise. + */ +if (hasHermesPromiseQueuedToJSVM) { + // Fast path for Hermes. + polyfillGlobal('queueMicrotask', () => global.HermesInternal.enqueueJob); +} else { + // Polyfill it with promise (regardless it's polyfiled or native) otherwise. + polyfillGlobal( + 'queueMicrotask', + () => require('./Timers/queueMicrotask.js').default, + ); +}