UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

200 lines (171 loc) 7.56 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ "sap/ui/test/autowaiter/_utils", "sap/ui/thirdparty/jquery", "sap/ui/test/autowaiter/_timeoutWaiter", "./WaiterBase" ], function (_utils, jQueryDOM, _timeoutWaiter, WaiterBase) { "use strict"; var aPromises = []; var OriginalPromise = window.Promise; // save it to avoid 'max call stack exceeded' var aStaticMethods = ["resolve", "reject", "all", "any", "race", "allSettled"]; var thenMicrotaskPromise; // defined only during .then() call var PromiseWaiter = WaiterBase.extend("sap.ui.test.autowaiter._promiseWaiter", { hasPending: function () { var aPendingPromises = aPromises.filter(function (mPromise) { return !!mPromise.pending && !_isPolling(mPromise); }); if (aPendingPromises.length > 0) { var sLogMessage = "There are " + aPendingPromises.length + " pending promises\n"; aPendingPromises.forEach(function (mPromise) { sLogMessage += _createLogForPromise(mPromise); }); oPromiseWaiter._oHasPendingLogger.debug(sLogMessage); } return aPendingPromises.length > 0; }, _getDefaultConfig: function () { return jQueryDOM.extend({ maxDelay: 1000 // milliseconds; should be at least as big as _timeoutWaiter maxDelay }, WaiterBase.prototype._getDefaultConfig.call(this)); }, _getValidationInfo: function () { return jQueryDOM.extend({ maxDelay: "numeric" }, WaiterBase.prototype._getValidationInfo.call(this)); } }); var oPromiseWaiter = new PromiseWaiter(); // register the extended initiator resolver that "sees" over promise .then() _timeoutWaiter._registerInitiatorResolverId(function() { return thenMicrotaskPromise ? thenMicrotaskPromise.completorTimeoutId : undefined; }); function _createLogForPromise(mPromise) { return "\nPromise: Args: " + mPromise.args + "\n Stack: " + mPromise.stack; } function _resolveInitiatorTimeoutId() { var initiatorTimeoutId = _timeoutWaiter._getInitiatorId(); if (!initiatorTimeoutId && thenMicrotaskPromise) { initiatorTimeoutId = thenMicrotaskPromise.completorTimeoutId; } return initiatorTimeoutId; } function _resolveCompletorTimeoutId() { var completorTimeoutId = _timeoutWaiter._getInitiatorId(); return completorTimeoutId; } function _trackPromise(sArguments) { var mPendingPromise = { args: sArguments, stack: _utils.resolveStackTrace(), pending: true, tooLate: false, initiatorTimeoutId: _resolveInitiatorTimeoutId(), longRunnerTimeoutId: setTimeout(function () { // Timeout to detect long runners mPendingPromise.tooLate = true; mPendingPromise.pending = false; oPromiseWaiter._oLogger.trace("Long-running promise is ignored:" + _createLogForPromise(mPendingPromise)); }, oPromiseWaiter._mConfig.maxDelay,'TIMEOUT_WAITER_IGNORE') }; oPromiseWaiter._oLogger.trace("New pending promise:" + _createLogForPromise(mPendingPromise)); aPromises.push(mPendingPromise); return mPendingPromise; } function _untrackPromise(mPendingPromise) { if (!mPendingPromise) { return; } // if initiatorId was not resolved initially, like in .then(new Promise) initial case, we will save it on resolve() mPendingPromise.completorTimeoutId = _resolveCompletorTimeoutId(); mPendingPromise.endTime = Date.now(); if (mPendingPromise.tooLate) { // the timeout already counted down - do nothing return; } else { // count down and clear the timeout to make sure it is only counted down once mPendingPromise.pending = false; oPromiseWaiter._oLogger.trace("Promise complete:" + _createLogForPromise(mPendingPromise)); clearTimeout(mPendingPromise.longRunnerTimeoutId); } } function _isPolling(mCurrentPromise) { // any promise created from polling timeout is assumed to be polling if (mCurrentPromise.initiatorTimeoutId && _timeoutWaiter._isPolling(mCurrentPromise.initiatorTimeoutId)) { oPromiseWaiter._oLogger.trace("Polling promise is ignored:" + _createLogForPromise(mCurrentPromise)); return true; } return false; } var WrappedPromise = function (fnOriginalExecutor, tracking) { var mPendingPromise; var fnWrappedExecutor = function (fnOriginalResolve, fnOriginalReject) { var sArguments = _utils.functionToString(fnOriginalExecutor); if (window.ES6Promise && sArguments === "'function noop() {}'") { oPromiseWaiter._oLogger.trace("Ignoring internal constructor of ES6 Promise polyfill"); return fnOriginalExecutor(fnOriginalResolve, fnOriginalReject); } else if (sArguments === "'function () { [native code] }'") { oPromiseWaiter._oLogger.trace("Ignoring internal Promise constructor"); return fnOriginalExecutor(fnOriginalResolve, fnOriginalReject); } else if (tracking === "PROMISE_WAITER_IGNORE") { oPromiseWaiter._oLogger.trace("Ignoring Promise marked to ignore"); return fnOriginalExecutor(fnOriginalResolve, fnOriginalReject); } else { mPendingPromise = _trackPromise(sArguments); var fnWrappedResolve = function wrappedResolve () { _untrackPromise(mPendingPromise); fnOriginalResolve.apply(this, arguments); }; var fnWrappedReject = function wrappedReject() { _untrackPromise(mPendingPromise); fnOriginalReject.apply(this, arguments); }; // call the original function which does the promised async work return fnOriginalExecutor(fnWrappedResolve, fnWrappedReject); } }; // create a Promise instance with the wrapped resolution and rejection functions var wrappedPromiseInstance = new OriginalPromise(fnWrappedExecutor); // override .then() method of the promise instance so we can track function _wrapPromiseInstanceThen(promiseInstance) { var fnOriginalThen = promiseInstance.then; promiseInstance.then = function wrappedThen(fnOriginalResolvedCallback,fnOriginalRejectedCallback) { var wrappedThenCallback = fnOriginalResolvedCallback; if (fnOriginalResolvedCallback) { var wrappedThenCallback = function wrappedThenCallback() { // save current promise in a global var so that everyonme down the call stack in the current microtask can use it thenMicrotaskPromise = mPendingPromise; var fnOriginalThenCallbackResult = fnOriginalResolvedCallback.apply(this,arguments); thenMicrotaskPromise = undefined; return fnOriginalThenCallbackResult; }; } var fnOriginalThenResult = fnOriginalThen.apply(this,[wrappedThenCallback,fnOriginalRejectedCallback]); // thenResult is a naitive promise and we need to override the .then() method again to cover the promise chaining scenario //return _wrapPromiseInstanceThen(fnOriginalThenResult); return fnOriginalThenResult; }; return promiseInstance; } // return the wrapped promise return _wrapPromiseInstanceThen(wrappedPromiseInstance); }; // copy instance methods (then, catch, finally). // make the prototype directly equal to the OriginalPromise prototype because we want // all old and new promise instances to be instanceof the OriginalPromise (to avoid type runtime errors) WrappedPromise.prototype = OriginalPromise.prototype; WrappedPromise.prototype.constructor = WrappedPromise; // add wrapped static methods aStaticMethods.forEach(function (sFunction) { if (OriginalPromise[sFunction]) { WrappedPromise[sFunction] = OriginalPromise[sFunction]; } }); // overwrite the global Promise object window.Promise = WrappedPromise; return oPromiseWaiter; }, true);