diff --git a/lib/amd/build/utils.min.js b/lib/amd/build/utils.min.js
index 76381ea4cf5..3d811a54e4f 100644
--- a/lib/amd/build/utils.min.js
+++ b/lib/amd/build/utils.min.js
@@ -1,3 +1,3 @@
-define("core/utils",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.throttle=_exports.debounce=void 0;_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};_exports.debounce=(func,wait)=>{let timeout=null;return function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];clearTimeout(timeout),timeout=setTimeout((()=>{func.apply(this,args)}),wait)}}}));
+define("core/utils",["exports","core/pending"],(function(_exports,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.throttle=_exports.debounce=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};const debounceMap=new Map;_exports.debounce=function(func,wait){let{pending:pending=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},timeout=null;const returnedFunction=function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];pending&&!debounceMap.has(returnedFunction)&&debounceMap.set(returnedFunction,new _pending.default("core/utils:debounce")),clearTimeout(timeout),timeout=setTimeout((async()=>{const pendingPromise=debounceMap.get(returnedFunction);debounceMap.delete(returnedFunction),await func.apply(undefined,args),null==pendingPromise||pendingPromise.resolve()}),wait)};return returnedFunction}}));
//# sourceMappingURL=utils.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/utils.min.js.map b/lib/amd/build/utils.min.js.map
index de85bde92db..21dcacaed7a 100644
--- a/lib/amd/build/utils.min.js.map
+++ b/lib/amd/build/utils.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Utility functions.\n *\n * @module core/utils\n * @copyright 2019 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n /**\n * Create a wrapper function to throttle the execution of the given\n *\n * function to at most once every specified period.\n *\n * If the function is attempted to be executed while it's in cooldown\n * (during the wait period) then it'll immediately execute again as\n * soon as the cooldown is over.\n *\n * @method\n * @param {Function} func The function to throttle\n * @param {Number} wait The number of milliseconds to wait between executions\n * @return {Function}\n */\nexport const throttle = (func, wait) => {\n let onCooldown = false;\n let runAgain = null;\n const run = function(...args) {\n if (runAgain === null) {\n // This is the first time the function has been called.\n runAgain = false;\n } else {\n // This function has been called a second time during the wait period\n // so re-run it once the wait period is over.\n runAgain = true;\n }\n\n if (onCooldown) {\n // Function has already run for this wait period.\n return;\n }\n\n func.apply(this, args);\n onCooldown = true;\n\n setTimeout(() => {\n const recurse = runAgain;\n onCooldown = false;\n runAgain = null;\n\n if (recurse) {\n run(args);\n }\n }, wait);\n };\n\n return run;\n};\n\n/**\n * Create a wrapper function to debounce the execution of the given\n * function. Each attempt to execute the function will reset the cooldown\n * period.\n *\n * @method\n * @param {Function} func The function to debounce\n * @param {Number} wait The number of milliseconds to wait after the final attempt to execute\n * @return {Function}\n */\nexport const debounce = (func, wait) => {\n let timeout = null;\n return function(...args) {\n clearTimeout(timeout);\n timeout = setTimeout(() => {\n func.apply(this, args);\n }, wait);\n };\n};\n"],"names":["func","wait","onCooldown","runAgain","run","args","apply","this","setTimeout","recurse","timeout","clearTimeout"],"mappings":"yKAqCwB,CAACA,KAAMC,YACvBC,YAAa,EACbC,SAAW,WACTC,IAAM,yCAAYC,6CAAAA,2BAGhBF,SAFa,OAAbA,SASAD,aAKJF,KAAKM,MAAMC,KAAMF,MACjBH,YAAa,EAEbM,YAAW,WACDC,QAAUN,SAChBD,YAAa,EACbC,SAAW,KAEPM,SACAL,IAAIC,QAETJ,eAGAG,uBAaa,CAACJ,KAAMC,YACvBS,QAAU,YACP,0CAAYL,kDAAAA,6BACfM,aAAaD,SACbA,QAAUF,YAAW,KACjBR,KAAKM,MAAMC,KAAMF,QAClBJ"}
\ No newline at end of file
+{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Utility functions.\n *\n * @module core/utils\n * @copyright 2019 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\n\n /**\n * Create a wrapper function to throttle the execution of the given\n *\n * function to at most once every specified period.\n *\n * If the function is attempted to be executed while it's in cooldown\n * (during the wait period) then it'll immediately execute again as\n * soon as the cooldown is over.\n *\n * @method\n * @param {Function} func The function to throttle\n * @param {Number} wait The number of milliseconds to wait between executions\n * @return {Function}\n */\nexport const throttle = (func, wait) => {\n let onCooldown = false;\n let runAgain = null;\n const run = function(...args) {\n if (runAgain === null) {\n // This is the first time the function has been called.\n runAgain = false;\n } else {\n // This function has been called a second time during the wait period\n // so re-run it once the wait period is over.\n runAgain = true;\n }\n\n if (onCooldown) {\n // Function has already run for this wait period.\n return;\n }\n\n func.apply(this, args);\n onCooldown = true;\n\n setTimeout(() => {\n const recurse = runAgain;\n onCooldown = false;\n runAgain = null;\n\n if (recurse) {\n run(args);\n }\n }, wait);\n };\n\n return run;\n};\n\n/**\n * @property {Map} debounceMap A map of functions to their debounced pending promises.\n */\nconst debounceMap = new Map();\n\n/**\n * Create a wrapper function to debounce the execution of the given\n * function. Each attempt to execute the function will reset the cooldown\n * period.\n *\n * @method\n * @param {Function} func The function to debounce\n * @param {Number} wait The number of milliseconds to wait after the final attempt to execute\n * @param {Object} [options]\n * @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise\n * @return {Function}\n */\nexport const debounce = (\n func,\n wait,\n {\n pending = false,\n } = {},\n) => {\n let timeout = null;\n\n const returnedFunction = (...args) => {\n if (pending && !debounceMap.has(returnedFunction)) {\n debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));\n }\n clearTimeout(timeout);\n timeout = setTimeout(async() => {\n // Get the current pending promise and immediately empty it.\n // This is important to allow the function to be debounced again as soon as possible.\n // We do not resolve it until later - but that's fine because the promise is appropriately scoped.\n const pendingPromise = debounceMap.get(returnedFunction);\n debounceMap.delete(returnedFunction);\n\n // Allow the debounced function to return a Promise.\n // This ensures that Behat will not continue until the function has finished executing.\n await func.apply(this, args);\n\n // Resolve the pending promise if it exists.\n pendingPromise?.resolve();\n }, wait);\n };\n\n return returnedFunction;\n};\n"],"names":["func","wait","onCooldown","runAgain","run","args","apply","this","setTimeout","recurse","debounceMap","Map","pending","timeout","returnedFunction","has","set","Pending","clearTimeout","async","pendingPromise","get","delete","resolve"],"mappings":"mQAuCwB,CAACA,KAAMC,YACvBC,YAAa,EACbC,SAAW,WACTC,IAAM,yCAAYC,6CAAAA,2BAGhBF,SAFa,OAAbA,SASAD,aAKJF,KAAKM,MAAMC,KAAMF,MACjBH,YAAa,EAEbM,YAAW,WACDC,QAAUN,SAChBD,YAAa,EACbC,SAAW,KAEPM,SACAL,IAAIC,QAETJ,eAGAG,WAMLM,YAAc,IAAIC,sBAcA,SACpBX,KACAC,UACAW,QACIA,SAAU,0DACV,GAEAC,QAAU,WAERC,iBAAmB,0CAAIT,kDAAAA,6BACrBO,UAAYF,YAAYK,IAAID,mBAC5BJ,YAAYM,IAAIF,iBAAkB,IAAIG,iBAAQ,wBAElDC,aAAaL,SACbA,QAAUL,YAAWW,gBAIXC,eAAiBV,YAAYW,IAAIP,kBACvCJ,YAAYY,OAAOR,wBAIbd,KAAKM,gBAAYD,MAGvBe,MAAAA,gBAAAA,eAAgBG,YACjBtB,cAGAa"}
\ No newline at end of file
diff --git a/lib/amd/src/utils.js b/lib/amd/src/utils.js
index 2483f1fe86e..4bd902162a7 100644
--- a/lib/amd/src/utils.js
+++ b/lib/amd/src/utils.js
@@ -21,6 +21,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+import Pending from 'core/pending';
+
/**
* Create a wrapper function to throttle the execution of the given
*
@@ -70,6 +72,11 @@ export const throttle = (func, wait) => {
return run;
};
+/**
+ * @property {Map} debounceMap A map of functions to their debounced pending promises.
+ */
+const debounceMap = new Map();
+
/**
* Create a wrapper function to debounce the execution of the given
* function. Each attempt to execute the function will reset the cooldown
@@ -78,14 +85,39 @@ export const throttle = (func, wait) => {
* @method
* @param {Function} func The function to debounce
* @param {Number} wait The number of milliseconds to wait after the final attempt to execute
+ * @param {Object} [options]
+ * @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise
* @return {Function}
*/
-export const debounce = (func, wait) => {
+export const debounce = (
+ func,
+ wait,
+ {
+ pending = false,
+ } = {},
+) => {
let timeout = null;
- return function(...args) {
+
+ const returnedFunction = (...args) => {
+ if (pending && !debounceMap.has(returnedFunction)) {
+ debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));
+ }
clearTimeout(timeout);
- timeout = setTimeout(() => {
- func.apply(this, args);
+ timeout = setTimeout(async() => {
+ // Get the current pending promise and immediately empty it.
+ // This is important to allow the function to be debounced again as soon as possible.
+ // We do not resolve it until later - but that's fine because the promise is appropriately scoped.
+ const pendingPromise = debounceMap.get(returnedFunction);
+ debounceMap.delete(returnedFunction);
+
+ // Allow the debounced function to return a Promise.
+ // This ensures that Behat will not continue until the function has finished executing.
+ await func.apply(this, args);
+
+ // Resolve the pending promise if it exists.
+ pendingPromise?.resolve();
}, wait);
};
+
+ return returnedFunction;
};