diff --git a/.size-limit.json b/.size-limit.json
index 81319fe2..c8131b57 100644
--- a/.size-limit.json
+++ b/.size-limit.json
@@ -2,21 +2,21 @@
{
"name": "es5-full",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "32.5 kb",
+ "limit": "33.5 kb",
"brotli": false,
"running": false
},
{
"name": "es6-full",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "31.5 kb",
+ "limit": "32 kb",
"brotli": false,
"running": false
},
{
"name": "es5-full-brotli",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "11.5 kb",
+ "limit": "12 kb",
"brotli": true,
"running": false
},
@@ -30,14 +30,14 @@
{
"name": "es5-zip",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "12.5 Kb",
+ "limit": "13 Kb",
"gzip": true,
"running": false
},
{
"name": "es6-zip",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "12.5 Kb",
+ "limit": "13 Kb",
"gzip": true,
"running": false
},
@@ -91,9 +91,25 @@
{
"name": "es5-simple-string",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "1kb",
+ "limit": "1 kb",
"import": "{ getWindow, strEndsWith }",
"gzip": true,
"running": false
+ },
+ {
+ "name": "es5-scheduleTimeout",
+ "path": "lib/dist/es5/mod/ts-utils.js",
+ "limit": "1 kb",
+ "import": "{ scheduleTimeout }",
+ "gzip": true,
+ "running": false
+ },
+ {
+ "name": "es5-scheduleMicrotask",
+ "path": "lib/dist/es5/mod/ts-utils.js",
+ "limit": "1.5 kb",
+ "import": "{ scheduleMicrotask }",
+ "gzip": true,
+ "running": false
}
]
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb8a3733..30705886 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+# Unreleased
+
+## Changelog
+
+### Features
+
+- Add microtask scheduling helpers with native `queueMicrotask`, Promise, and timer-backed fallbacks
+ - New functions: `scheduleMicrotask`, `hasQueueMicrotask`, `getQueueMicrotask`, `setMicroTaskFallbackOptions`
+ - New public types: `ScheduleMicrotaskFn`, `MicroTaskOptions`
+ - Extends microtask support by providing cancellable microtasks via `ITimerHandler`, plus fallback ordering to run microtasks before queued timers when using the timer-backed implementation
+ - Provides runtime parity across all supported environments by using native `queueMicrotask` when present, Promise-backed scheduling when available, and a timer-backed microtask queue otherwise
+
# v0.14.0 May 18th, 2026
## Changelog
@@ -5,7 +17,7 @@
### Features
- [#525](https://github.com/nevware21/ts-utils/pull/525) feat(array): add new array helpers and array-like detection
- - New helpers: `isArrayLike`, `arrSlice`, and other array utility improvements
+ - New helpers: `isArrayLike`, `arrUnique`, `arrCompact`, `arrFlatten`, `arrGroupBy`, `arrChunk` and export previously missed `isArrayLike`
- [#527](https://github.com/nevware21/ts-utils/pull/527) feat(string): add `strReplace` and `strReplaceAll` helpers with refactored internal replacements
- [#528](https://github.com/nevware21/ts-utils/pull/528) feat(string): add `strCapitalizeWords` helper
- [#529](https://github.com/nevware21/ts-utils/pull/529) / [#530](https://github.com/nevware21/ts-utils/pull/530) feat(string): add `strTruncate`, `strCount`, `strAt`, and `strMatchAll` helpers with shared literal regex helper
diff --git a/README.md b/README.md
index 92cc4bbf..d3c5899c 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,7 @@ npm install @nevware21/ts-utils --save
- Consistent timing APIs across environments
- Performance measurement utilities
- Scheduling helpers with automatic polyfills
+- Microtask scheduling helpers that extend standard microtasks with cancellable handlers, with cross-runtime parity for Node.js, browsers, and web workers
- [Customizable timeout handling](https://nevware21.github.io/ts-utils/timeout-overrides.html) via package-level and global overrides
For advanced timeout customization options, including global overrides, see our [Timeout Overrides Guide](https://nevware21.github.io/ts-utils/timeout-overrides.html).
@@ -114,7 +115,7 @@ Below is a categorized list of all available utilities with direct links to thei
| Type | Functions / Helpers / Aliases / Polyfills
|----------------------------|---------------------------------------------------
-| Runtime Environment Checks | [getCancelIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getCancelIdleCallback.html)(); [getDocument](https://nevware21.github.io/ts-utils/typedoc/functions/getDocument.html)(); [getGlobal](https://nevware21.github.io/ts-utils/typedoc/functions/getGlobal.html)(); [getHistory](https://nevware21.github.io/ts-utils/typedoc/functions/getHistory.html)(); [getIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getIdleCallback.html)(); [getInst](https://nevware21.github.io/ts-utils/typedoc/functions/getInst.html)(); [getNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/getNavigator.html)(); [getPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/getPerformance.html)(); [getWindow](https://nevware21.github.io/ts-utils/typedoc/functions/getWindow.html)(); [hasDocument](https://nevware21.github.io/ts-utils/typedoc/functions/hasDocument.html)(); [hasHistory](https://nevware21.github.io/ts-utils/typedoc/functions/hasHistory.html)(); [hasNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/hasNavigator.html)(); [hasPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/hasPerformance.html)(); [hasWindow](https://nevware21.github.io/ts-utils/typedoc/functions/hasWindow.html)(); [isNode](https://nevware21.github.io/ts-utils/typedoc/functions/isNode.html)(); [isWebWorker](https://nevware21.github.io/ts-utils/typedoc/functions/isWebWorker.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)();
+| Runtime Environment Checks | [getCancelIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getCancelIdleCallback.html)(); [getDocument](https://nevware21.github.io/ts-utils/typedoc/functions/getDocument.html)(); [getGlobal](https://nevware21.github.io/ts-utils/typedoc/functions/getGlobal.html)(); [getHistory](https://nevware21.github.io/ts-utils/typedoc/functions/getHistory.html)(); [getIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getIdleCallback.html)(); [getInst](https://nevware21.github.io/ts-utils/typedoc/functions/getInst.html)(); [getNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/getNavigator.html)(); [getPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/getPerformance.html)(); [getQueueMicrotask](https://nevware21.github.io/ts-utils/typedoc/functions/getQueueMicrotask.html)(); [getWindow](https://nevware21.github.io/ts-utils/typedoc/functions/getWindow.html)(); [hasDocument](https://nevware21.github.io/ts-utils/typedoc/functions/hasDocument.html)(); [hasHistory](https://nevware21.github.io/ts-utils/typedoc/functions/hasHistory.html)(); [hasNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/hasNavigator.html)(); [hasPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/hasPerformance.html)(); [hasQueueMicrotask](https://nevware21.github.io/ts-utils/typedoc/functions/hasQueueMicrotask.html)(); [hasWindow](https://nevware21.github.io/ts-utils/typedoc/functions/hasWindow.html)(); [isNode](https://nevware21.github.io/ts-utils/typedoc/functions/isNode.html)(); [isWebWorker](https://nevware21.github.io/ts-utils/typedoc/functions/isWebWorker.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)();
| Type Identity | [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)(); [isArrayLike](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayLike.html)(); [isArrayBuffer](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayBuffer.html)(); [isAsyncFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncFunction.html)(); [isAsyncGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncGenerator.html)(); [isAsyncIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncIterable.html)(); [isBigInt](https://nevware21.github.io/ts-utils/typedoc/functions/isBigInt.html)(); [isBlob](https://nevware21.github.io/ts-utils/typedoc/functions/isBlob.html)(); [isBoolean](https://nevware21.github.io/ts-utils/typedoc/functions/isBoolean.html)(); [isDate](https://nevware21.github.io/ts-utils/typedoc/functions/isDate.html)(); [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [isFile](https://nevware21.github.io/ts-utils/typedoc/functions/isFile.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isFormData](https://nevware21.github.io/ts-utils/typedoc/functions/isFormData.html)(); [isFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isFunction.html)(); [isGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isGenerator.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIntegerInRange](https://nevware21.github.io/ts-utils/typedoc/functions/isIntegerInRange.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [isMap](https://nevware21.github.io/ts-utils/typedoc/functions/isMap.html)(); [isMapLike](https://nevware21.github.io/ts-utils/typedoc/functions/isMapLike.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isPlainObject](https://nevware21.github.io/ts-utils/typedoc/functions/isPlainObject.html)(); [isPrimitive](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitive.html)(); [isPrimitiveType](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitiveType.html)(); [isPromise](https://nevware21.github.io/ts-utils/typedoc/functions/isPromise.html)(); [isPromiseLike](https://nevware21.github.io/ts-utils/typedoc/functions/isPromiseLike.html)(); [isRegExp](https://nevware21.github.io/ts-utils/typedoc/functions/isRegExp.html)(); [isSet](https://nevware21.github.io/ts-utils/typedoc/functions/isSet.html)(); [isSetLike](https://nevware21.github.io/ts-utils/typedoc/functions/isSetLike.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [isThenable](https://nevware21.github.io/ts-utils/typedoc/functions/isThenable.html)(); [isTypeof](https://nevware21.github.io/ts-utils/typedoc/functions/isTypeof.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); [isWeakMap](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakMap.html)(); [isWeakSet](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakSet.html)();
| Value Check | [hasValue](https://nevware21.github.io/ts-utils/typedoc/functions/hasValue.html)(); [isDefined](https://nevware21.github.io/ts-utils/typedoc/functions/isDefined.html)(); [isEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/isEmpty.html)(); [isNotTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isNotTruthy.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isTruthy.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)();
| Value | [getValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByKey.html)(); [setValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByKey.html)(); [getValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByIter.html)(); [setValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByIter.html)(); [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)();
@@ -132,7 +133,7 @@ Below is a categorized list of all available utilities with direct links to thei
| Object | [deepExtend](https://nevware21.github.io/ts-utils/typedoc/functions/deepExtend.html)(); [forEachOwnKey](https://nevware21.github.io/ts-utils/typedoc/functions/forEachOwnKey.html)(); [forEachOwnKeySafe](https://nevware21.github.io/ts-utils/typedoc/functions/forEachOwnKeySafe.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isUnsafePropKey](https://nevware21.github.io/ts-utils/typedoc/functions/isUnsafePropKey.html)(); [isUnsafeTarget](https://nevware21.github.io/ts-utils/typedoc/functions/isUnsafeTarget.html)(); [objAssign](https://nevware21.github.io/ts-utils/typedoc/functions/objAssign.html)(); [objCopyProps](https://nevware21.github.io/ts-utils/typedoc/functions/objCopyProps.html)(); [objCreate](https://nevware21.github.io/ts-utils/typedoc/functions/objCreate.html)(); [objDeepCopy](https://nevware21.github.io/ts-utils/typedoc/functions/objDeepCopy.html)(); [objDeepFreeze](https://nevware21.github.io/ts-utils/typedoc/functions/objDeepFreeze.html)(); [objDefaults](https://nevware21.github.io/ts-utils/typedoc/functions/objDefaults.html)(); [objDefine](https://nevware21.github.io/ts-utils/typedoc/functions/objDefine.html)(); [objDefineAccessors](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineAccessors.html)(); [objDefineGet](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineGet.html)(); [objDefineProp](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProp.html)(); [objDefineProps](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProps.html)(); [objDefineProperties](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProperties.html)(); [objDiff](https://nevware21.github.io/ts-utils/typedoc/functions/objDiff.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objExtend](https://nevware21.github.io/ts-utils/typedoc/functions/objExtend.html)(); [objForEachKey](https://nevware21.github.io/ts-utils/typedoc/functions/objForEachKey.html)(); [objForEachKeySafe](https://nevware21.github.io/ts-utils/typedoc/functions/objForEachKeySafe.html)(); [objFreeze](https://nevware21.github.io/ts-utils/typedoc/functions/objFreeze.html)(); [objFromEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objFromEntries.html)(); [objGetOwnPropertyDescriptor](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyDescriptor.html)(); [objGetOwnPropertyDescriptors](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyDescriptors.html)(); [objGetOwnPropertyNames](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyNames.html)(); [objGetOwnPropertySymbols](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertySymbols.html)(); [objHasOwn](https://nevware21.github.io/ts-utils/typedoc/functions/objHasOwn.html)(); [objHasOwnProperty](https://nevware21.github.io/ts-utils/typedoc/functions/objHasOwnProperty.html)(); [objIs](https://nevware21.github.io/ts-utils/typedoc/functions/objIs.html)(); [objIsExtensible](https://nevware21.github.io/ts-utils/typedoc/functions/objIsExtensible.html)(); [objIsFrozen](https://nevware21.github.io/ts-utils/typedoc/functions/objIsFrozen.html)(); [objIsSealed](https://nevware21.github.io/ts-utils/typedoc/functions/objIsSealed.html)(); [objKeys](https://nevware21.github.io/ts-utils/typedoc/functions/objKeys.html)(); [objMapValues](https://nevware21.github.io/ts-utils/typedoc/functions/objMapValues.html)(); [objMergeIf](https://nevware21.github.io/ts-utils/typedoc/functions/objMergeIf.html)(); [objOmit](https://nevware21.github.io/ts-utils/typedoc/functions/objOmit.html)(); [objOmitBy](https://nevware21.github.io/ts-utils/typedoc/functions/objOmitBy.html)(); [objPick](https://nevware21.github.io/ts-utils/typedoc/functions/objPick.html)(); [objPickBy](https://nevware21.github.io/ts-utils/typedoc/functions/objPickBy.html)(); [objPreventExtensions](https://nevware21.github.io/ts-utils/typedoc/functions/objPreventExtensions.html)(); [objPropertyIsEnumerable](https://nevware21.github.io/ts-utils/typedoc/functions/objPropertyIsEnumerable.html)(); [objSeal](https://nevware21.github.io/ts-utils/typedoc/functions/objSeal.html)(); [objGetPrototypeOf](https://nevware21.github.io/ts-utils/typedoc/functions/objGetPrototypeOf.html)(); [objSetPrototypeOf](https://nevware21.github.io/ts-utils/typedoc/functions/objSetPrototypeOf.html)(); [objToString](https://nevware21.github.io/ts-utils/typedoc/functions/objToString.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)();
[polyObjEntries](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjEntries.html)(); [polyObjIs](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjIs.html)(); [polyObjKeys](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjKeys.html)();
| String | [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [strCount](https://nevware21.github.io/ts-utils/typedoc/functions/strCount.html)(); [strEndsWith](https://nevware21.github.io/ts-utils/typedoc/functions/strEndsWith.html)(); [strIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/strIndexOf.html)(); [strIsNullOrEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/strIsNullOrEmpty.html)(); [strIsNullOrWhiteSpace](https://nevware21.github.io/ts-utils/typedoc/functions/strIsNullOrWhiteSpace.html)(); [strLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/strLastIndexOf.html)(); [strLeft](https://nevware21.github.io/ts-utils/typedoc/functions/strLeft.html)(); [strPadEnd](https://nevware21.github.io/ts-utils/typedoc/functions/strPadEnd.html)(); [strPadStart](https://nevware21.github.io/ts-utils/typedoc/functions/strPadStart.html)(); [strRepeat](https://nevware21.github.io/ts-utils/typedoc/functions/strRepeat.html)(); [strReplace](https://nevware21.github.io/ts-utils/typedoc/functions/strReplace.html)(); [strReplaceAll](https://nevware21.github.io/ts-utils/typedoc/functions/strReplaceAll.html)(); [strRight](https://nevware21.github.io/ts-utils/typedoc/functions/strRight.html)(); [strSlice](https://nevware21.github.io/ts-utils/typedoc/functions/strSlice.html)(); [strSplit](https://nevware21.github.io/ts-utils/typedoc/functions/strSplit.html)(); [strStartsWith](https://nevware21.github.io/ts-utils/typedoc/functions/strStartsWith.html)(); [strSubstr](https://nevware21.github.io/ts-utils/typedoc/functions/strSubstr.html)(); [strSubstring](https://nevware21.github.io/ts-utils/typedoc/functions/strSubstring.html)(); [strSymSplit](https://nevware21.github.io/ts-utils/typedoc/functions/strSymSplit.html)(); [strTruncate](https://nevware21.github.io/ts-utils/typedoc/functions/strTruncate.html)(); [strTrim](https://nevware21.github.io/ts-utils/typedoc/functions/strTrim.html)(); [strTrimEnd](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimEnd.html)(); [strTrimLeft](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimLeft.html)(); [strTrimRight](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimRight.html)(); [strTrimStart](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimStart.html)(); [strLetterCase](https://nevware21.github.io/ts-utils/typedoc/functions/strLetterCase.html)(); [strCapitalizeWords](https://nevware21.github.io/ts-utils/typedoc/functions/strCapitalizeWords.html)(); [strCamelCase](https://nevware21.github.io/ts-utils/typedoc/functions/strCamelCase.html)(); [strKebabCase](https://nevware21.github.io/ts-utils/typedoc/functions/strKebabCase.html)(); [strSnakeCase](https://nevware21.github.io/ts-utils/typedoc/functions/strSnakeCase.html)(); [strUpper](https://nevware21.github.io/ts-utils/typedoc/functions/strUpper.html)(); [strLower](https://nevware21.github.io/ts-utils/typedoc/functions/strLower.html)(); [strContains](https://nevware21.github.io/ts-utils/typedoc/functions/strContains.html)(); [strIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/strIncludes.html)();
[polyStrSubstr](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrSubstr.html)(); [polyStrTrim](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrim.html)(); [polyStrTrimEnd](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrimEnd.html)(); [polyStrTrimStart](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrimStart.html)(); [polyStrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrIncludes.html)();
| Symbol | [WellKnownSymbols](https://nevware21.github.io/ts-utils/typedoc/enums/WellKnownSymbols.html) (const enum);
[getKnownSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/getKnownSymbol.html)(); [getSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/getSymbol.html)(); [hasSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/hasSymbol.html)(); [isSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/isSymbol.html)(); [newSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/newSymbol.html)(); [symbolFor](https://nevware21.github.io/ts-utils/typedoc/functions/symbolFor.html)(); [symbolKeyFor](https://nevware21.github.io/ts-utils/typedoc/functions/symbolKeyFor.html)();
[polyGetKnownSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/polyGetKnownSymbol.html)(); [polyNewSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/polyNewSymbol.html)(); [polySymbolFor](https://nevware21.github.io/ts-utils/typedoc/functions/polySymbolFor.html)(); [polySymbolKeyFor](https://nevware21.github.io/ts-utils/typedoc/functions/polySymbolKeyFor.html)();
Polyfills are used to automatically backfill runtimes that do not support `Symbol`, not all of the Symbol functionality is provided.
-| Timer | [createTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/createTimeout.html)(); [createTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/createTimeoutWith.html)(); [elapsedTime](https://nevware21.github.io/ts-utils/typedoc/functions/elapsedTime.html)(); [perfNow](https://nevware21.github.io/ts-utils/typedoc/functions/perfNow.html)(); [setGlobalTimeoutOverrides](https://nevware21.github.io/ts-utils/typedoc/functions/setGlobalTimeoutOverrides.html)(); [setTimeoutOverrides](https://nevware21.github.io/ts-utils/typedoc/functions/setTimeoutOverrides.html)(); [utcNow](https://nevware21.github.io/ts-utils/typedoc/functions/utcNow.html)(); [scheduleIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleIdleCallback.html)(); [scheduleInterval](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleInterval.html)(); [scheduleTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeout.html)(); [scheduleTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeoutWith.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)();
For runtimes that don't support `requestIdleCallback` normal setTimeout() is used with the values from [`setDefaultIdleTimeout`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultIdleTimeout.html)() and [`setDefaultMaxExecutionTime`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultMaxExecutionTime.html)();
[polyUtcNow](https://nevware21.github.io/ts-utils/typedoc/functions/polyUtcNow.html)();
+| Timer | [createTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/createTimeout.html)(); [createTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/createTimeoutWith.html)(); [elapsedTime](https://nevware21.github.io/ts-utils/typedoc/functions/elapsedTime.html)(); [perfNow](https://nevware21.github.io/ts-utils/typedoc/functions/perfNow.html)(); [setGlobalTimeoutOverrides](https://nevware21.github.io/ts-utils/typedoc/functions/setGlobalTimeoutOverrides.html)(); [setMicroTaskFallbackOptions](https://nevware21.github.io/ts-utils/typedoc/functions/setMicroTaskFallbackOptions.html)(); [setTimeoutOverrides](https://nevware21.github.io/ts-utils/typedoc/functions/setTimeoutOverrides.html)(); [utcNow](https://nevware21.github.io/ts-utils/typedoc/functions/utcNow.html)(); [scheduleIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleIdleCallback.html)(); [scheduleInterval](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleInterval.html)(); [scheduleMicrotask](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleMicrotask.html)(); [scheduleTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeout.html)(); [scheduleTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeoutWith.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)();
Microtask helpers extend standard microtasks with cancellable handlers, and provide parity across all supported runtimes by using native `queueMicrotask` when present, Promise-backed scheduling when available, and a timer-backed queue otherwise.
For runtimes that don't support `requestIdleCallback` normal setTimeout() is used with the values from [`setDefaultIdleTimeout`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultIdleTimeout.html)() and [`setDefaultMaxExecutionTime`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultMaxExecutionTime.html)();
[polyUtcNow](https://nevware21.github.io/ts-utils/typedoc/functions/polyUtcNow.html)();
| Conversion & Encoding | [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [encodeAsBase64](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsBase64.html)(); [decodeBase64](https://nevware21.github.io/ts-utils/typedoc/functions/decodeBase64.html)(); [encodeAsBase64Url](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsBase64Url.html)(); [decodeBase64Url](https://nevware21.github.io/ts-utils/typedoc/functions/decodeBase64Url.html)(); [encodeAsHex](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHex.html)(); [decodeHex](https://nevware21.github.io/ts-utils/typedoc/functions/decodeHex.html)(); [encodeAsUri](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsUri.html)(); [decodeUri](https://nevware21.github.io/ts-utils/typedoc/functions/decodeUri.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)(); [strLetterCase](https://nevware21.github.io/ts-utils/typedoc/functions/strLetterCase.html)(); [strCapitalizeWords](https://nevware21.github.io/ts-utils/typedoc/functions/strCapitalizeWords.html)(); [strCamelCase](https://nevware21.github.io/ts-utils/typedoc/functions/strCamelCase.html)(); [strKebabCase](https://nevware21.github.io/ts-utils/typedoc/functions/strKebabCase.html)(); [strSnakeCase](https://nevware21.github.io/ts-utils/typedoc/functions/strSnakeCase.html)(); [strUpper](https://nevware21.github.io/ts-utils/typedoc/functions/strUpper.html)(); [strLower](https://nevware21.github.io/ts-utils/typedoc/functions/strLower.html)();
| Cache | [createCachedValue](https://nevware21.github.io/ts-utils/typedoc/functions/createCachedValue.html)(); [createDeferredCachedValue](https://nevware21.github.io/ts-utils/typedoc/functions/createDeferredCachedValue.html)(); [getDeferred](https://nevware21.github.io/ts-utils/typedoc/functions/getDeferred.html)(); [getWritableDeferred](https://nevware21.github.io/ts-utils/typedoc/functions/getWritableDeferred.html)();
| Lazy | [getLazy](https://nevware21.github.io/ts-utils/typedoc/functions/getLazy.html)(); [getWritableLazy](https://nevware21.github.io/ts-utils/typedoc/functions/getWritableLazy.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); [safeGetLazy](https://nevware21.github.io/ts-utils/typedoc/functions/safeGetLazy.html)(); [setBypassLazyCache](https://nevware21.github.io/ts-utils/typedoc/functions/setBypassLazyCache.html)();
diff --git a/lib/package.json b/lib/package.json
index 3ca314dd..e9864043 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -105,6 +105,9 @@
"idle timer",
"timer",
"interval",
+ "microtask",
+ "queueMicrotask",
+ "cancellable microtask",
"includes",
"string contains",
"html encode",
diff --git a/lib/src/index.ts b/lib/src/index.ts
index 1b256813..064df429 100644
--- a/lib/src/index.ts
+++ b/lib/src/index.ts
@@ -76,7 +76,7 @@ export {
export { deepExtend, objExtend } from "./helpers/extend";
export { getValueByKey, setValueByKey, getValueByIter, setValueByIter } from "./helpers/get_set_value";
export { ILazyValue, getLazy, setBypassLazyCache, getWritableLazy } from "./helpers/lazy";
-export { IGetLength as GetLengthImpl, getLength } from "./helpers/length";
+export { IGetLength, IGetLength as GetLengthImpl, getLength } from "./helpers/length";
export { getIntValue, isInteger, isIntegerInRange, isFiniteNumber } from "./helpers/number";
export { getPerformance, hasPerformance, elapsedTime, perfNow } from "./helpers/perf";
export { createFilenameRegex, createLiteralRegex, createWildcardRegex, makeGlobRegex } from "./helpers/regexp";
@@ -166,6 +166,10 @@ export {
getIdleCallback, getCancelIdleCallback, RequestIdleCallback, CancelIdleCallback
} from "./timer/idle";
export { scheduleInterval } from "./timer/interval";
+export {
+ hasQueueMicrotask, scheduleMicrotask, getQueueMicrotask,
+ ScheduleMicrotaskFn, MicroTaskOptions, setMicroTaskFallbackOptions
+} from "./timer/microtask";
export {
TimeoutOverrideFn, ClearTimeoutOverrideFn, TimeoutOverrideFuncs, scheduleTimeout, scheduleTimeoutWith,
createTimeout, createTimeoutWith, setTimeoutOverrides, setGlobalTimeoutOverrides
diff --git a/lib/src/timer/microtask.ts b/lib/src/timer/microtask.ts
new file mode 100644
index 00000000..00bb04dc
--- /dev/null
+++ b/lib/src/timer/microtask.ts
@@ -0,0 +1,227 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { isStrictUndefined } from "../helpers/base";
+import { _getGlobalInstFn, getInst } from "../helpers/environment";
+import { ITimerHandler, _TimerHandler, _createTimerHandler } from "./handler";
+import { _getPromiseMicrotaskFn } from "./microtasks/promiseMicrotask";
+import { _addMicrotaskToQueue } from "./microtasks/timerMicrotask";
+
+let _defaultOptions: MicroTaskOptions | undefined;
+
+/**
+ * Type alias for a microtask callback function, which is a function that is scheduled to run in the microtask
+ * queue after the current execution context completes.
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ */
+export type MicrotaskFn = () => void;
+
+/**
+ * Type alias for a function that is used to schedule a microtask, which is a function
+ * that takes a callback and schedules it to run
+ *
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ * @param callback - The microtask callback function to schedule.
+ */
+export type ScheduleMicrotaskFn = (callback: MicrotaskFn) => void;
+
+/**
+ * Controls how `scheduleMicrotask` chooses fallback behavior when native
+ * `queueMicrotask` is not available.
+ *
+ * @since 0.15.0
+ * @group Timer
+ */
+export interface MicroTaskOptions {
+ /**
+ * Provide a custom scheduling function to use when native `queueMicrotask` is unavailable.
+ * When specified, this takes precedence over both the Promise fallback and the timer-backed
+ * queue fallback, regardless of whether Promise support is available.
+ */
+ scheduleFn?: ScheduleMicrotaskFn;
+
+ /**
+ * When `true`, skips the Promise fallback and uses `scheduleTimeout(..., 0)` instead. When
+ * `scheduleFn` is not provided, this option controls whether to use `scheduleTimeout(..., 0)`
+ * as the fallback instead of `Promise.resolve().then(...)`.
+ * A per-call value of `false` explicitly opts back in to the Promise fallback even when the global
+ * default set via {@link setMicroTaskFallbackOptions} has `useTimeout: true`.
+ */
+ useTimeout?: boolean;
+}
+
+/**
+ * Returns the global `queueMicrotask` function if available, or `null` when unavailable.
+ *
+ * @function
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ * @example
+ * ```ts
+ * const queueFn = getQueueMicrotask();
+ * if (queueFn) {
+ * queueFn(() => {
+ * console.log("microtask");
+ * });
+ * }
+ * ```
+ */
+export const getQueueMicrotask = (/*#__PURE__*/_getGlobalInstFn(getInst as any, ["queueMicrotask"]));
+
+/**
+ * Identifies if the runtime supports the `queueMicrotask` API.
+ *
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ * @returns True if the runtime supports `queueMicrotask` otherwise false.
+ * @example
+ * ```ts
+ * if (hasQueueMicrotask()) {
+ * console.log("Native queueMicrotask support is available");
+ * }
+ * ```
+ */
+/*#__NO_SIDE_EFFECTS__*/
+export function hasQueueMicrotask(): boolean {
+ return !!( /*#__PURE__*/getQueueMicrotask());
+}
+
+/**
+ * Sets the default fallback behavior for {@link scheduleMicrotask} when
+ * `queueMicrotask` is not available.
+ *
+ * @since 0.15.0
+ * @group Timer
+ *
+ * @param options - The fallback options to apply. Passing `undefined` resets
+ * options to defaults.
+ * @example
+ * ```ts
+ * setMicroTaskFallbackOptions({
+ * useTimeout: true
+ * });
+ *
+ * scheduleMicrotask(() => {
+ * console.log("timer-backed microtask fallback");
+ * });
+ *
+ * setMicroTaskFallbackOptions();
+ * ```
+ */
+export function setMicroTaskFallbackOptions(options?: MicroTaskOptions): void {
+ _defaultOptions = options;
+}
+
+/**
+ * Schedules a callback to run in the microtask queue.
+ *
+ * It uses the native `queueMicrotask` when available, otherwise falls back to
+ * `Promise.resolve().then(...)`, and if `Promise` is unavailable it falls back to
+ * `scheduleTimeout(..., 0)`.
+ * Unlike standard microtasks, this helper returns a cancellable `ITimerHandler`
+ * so scheduled callbacks can be canceled before execution.
+ * This provides consistent microtask scheduling behavior across all supported runtimes,
+ * including Node.js, browsers, and web workers.
+ *
+ * @since 0.15.0
+ * @group Timer
+ *
+ * @param callback - The callback to execute.
+ * @param options - Optional per-call fallback options when `queueMicrotask` is unavailable.
+ * @returns A handler that can be used to cancel or refresh the scheduled callback.
+ * @example
+ * ```ts
+ * let order: string[] = [];
+ * order.push("sync");
+ *
+ * const handler = scheduleMicrotask(() => {
+ * order.push("microtask");
+ * });
+ *
+ * scheduleTimeout(() => {
+ * order.push("timeout");
+ * }, 0);
+ *
+ * // order becomes ["sync", "microtask", "timeout"]
+ * handler.enabled; // true until the microtask executes
+ * ```
+ * @example
+ * ```ts
+ * scheduleMicrotask(() => {
+ * console.log("custom fallback scheduler");
+ * }, {
+ * scheduleFn: (cb) => {
+ * setTimeout(cb, 0);
+ * }
+ * });
+ * ```
+ */
+export function scheduleMicrotask(callback: () => void, options?: MicroTaskOptions): ITimerHandler {
+ let scheduleFn: ScheduleMicrotaskFn | undefined;
+ let queueMicrotaskFn = getQueueMicrotask();
+
+ if (!queueMicrotaskFn) {
+ // Do we have a custom schedule function to use
+ scheduleFn = (options && options.scheduleFn) || (_defaultOptions && _defaultOptions.scheduleFn);
+ if (!scheduleFn) {
+ // Per-call options.useTimeout takes full precedence over the global default when it is
+ // explicitly provided (even as false), so only fall back to _defaultOptions when the per-call
+ // value is undefined/absent. usePromise is the inverse: true unless useTimeout is explicitly set.
+ let usePromise = !((options && !isStrictUndefined(options.useTimeout))
+ ? options.useTimeout
+ : (_defaultOptions && _defaultOptions.useTimeout));
+ if (usePromise) {
+ // Use the Promise based fallback if available, otherwise use setTimeout
+ queueMicrotaskFn = _getPromiseMicrotaskFn();
+ }
+ }
+ }
+
+ return _createCancellableMicroTask(callback, scheduleFn || queueMicrotaskFn || _addMicrotaskToQueue);
+}
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+function _createCancellableMicroTask(callback: () => void, queueFn: ScheduleMicrotaskFn): ITimerHandler {
+ let handler: _TimerHandler;
+ // Used to track the currently scheduled task, incremented to cancel pending tasks when needed
+ let currentTask = 0;
+
+ function _scheduleTask() {
+ let taskId = ++currentTask;
+ queueFn(() => {
+ if (taskId === currentTask) {
+ handler.dn();
+ callback();
+ }
+ });
+
+ return taskId;
+ }
+
+ function _cancelTask(taskId: number) {
+ if (taskId === currentTask) {
+ currentTask++;
+ }
+ }
+
+ handler = _createTimerHandler(false, _scheduleTask, _cancelTask);
+ // Start the timer only after handler is fully assigned so that any synchronous
+ // queueFn implementation that fires the callback immediately can safely access handler.dn().
+ handler.h.refresh();
+
+ return handler.h;
+}
diff --git a/lib/src/timer/microtasks/promiseMicrotask.ts b/lib/src/timer/microtasks/promiseMicrotask.ts
new file mode 100644
index 00000000..348f61ae
--- /dev/null
+++ b/lib/src/timer/microtasks/promiseMicrotask.ts
@@ -0,0 +1,54 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { isFunction } from "../../helpers/base";
+import { createCachedValue, ICachedValue } from "../../helpers/cache";
+import { getInst } from "../../helpers/environment";
+import { _globalLazyTestHooks, _initTestHooks } from "../../helpers/lazy";
+import { UNDEF_VALUE } from "../../internal/constants";
+import { ScheduleMicrotaskFn } from "../microtask";
+import { _runMicroTask } from "./runMicrotask";
+
+let _promiseFn: ICachedValue;
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+function _promiseScheduleFn(promiseCls: PromiseConstructor): ScheduleMicrotaskFn {
+ return function(callback: () => void): void {
+ promiseCls.resolve().then(() => {
+ _runMicroTask(callback);
+ });
+ };
+}
+
+/**
+ * @internal
+ * Resolves the Promise-based scheduler from the current global state without caching.
+ * @since 0.15.0
+ */
+function _resolvePromiseFn(): ScheduleMicrotaskFn | undefined {
+ let promiseCls = getInst("Promise");
+ return (promiseCls && isFunction(promiseCls.resolve)) ? _promiseScheduleFn(promiseCls) : UNDEF_VALUE as any;
+}
+
+/**
+ * @internal
+ * Returns the Promise-based microtask scheduler, re-resolving the global Promise when the lazy
+ * bypass flag is active (e.g. during tests) so that changes to the global Promise are reflected.
+ * @since 0.15.0
+ */
+export function _getPromiseMicrotaskFn(): ScheduleMicrotaskFn | null {
+ !_globalLazyTestHooks && _initTestHooks();
+ if (!_promiseFn || _globalLazyTestHooks.lzy) {
+ _promiseFn = createCachedValue(_resolvePromiseFn());
+ }
+
+ return _promiseFn.v || null;
+}
\ No newline at end of file
diff --git a/lib/src/timer/microtasks/runMicrotask.ts b/lib/src/timer/microtasks/runMicrotask.ts
new file mode 100644
index 00000000..5f8d7ea6
--- /dev/null
+++ b/lib/src/timer/microtasks/runMicrotask.ts
@@ -0,0 +1,30 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { MicrotaskFn } from "../microtask";
+import { scheduleTimeout } from "../timeout";
+
+/**
+ * @internal
+ * Runs a microtask callback and ensures that any exceptions thrown are re-thrown in a new task to avoid swallowing errors.
+ * This is necessary because when using the fallback for microtasks, any exceptions thrown in the callback will be caught
+ * and not re-thrown, which can lead to silent failures. By catching exceptions and re-throwing them in a new task
+ * (using `scheduleTimeout`), we ensure that errors are properly surfaced even when using the Promise or timeout fallback.
+ * @since 0.15.0
+ * @param callback - The callback to execute.
+ */
+export function _runMicroTask(callback: MicrotaskFn): void {
+ try {
+ callback();
+ } catch (e) {
+ scheduleTimeout(() => {
+ throw e;
+ }, 0);
+ }
+}
+
diff --git a/lib/src/timer/microtasks/timerMicrotask.ts b/lib/src/timer/microtasks/timerMicrotask.ts
new file mode 100644
index 00000000..33fb7240
--- /dev/null
+++ b/lib/src/timer/microtasks/timerMicrotask.ts
@@ -0,0 +1,93 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { UNDEF_VALUE } from "../../internal/constants";
+import { ITimerHandler } from "../handler";
+import { MicrotaskFn } from "../microtask";
+import { _setMicrotaskCallback, scheduleTimeout } from "../timeout";
+import { _runMicroTask } from "./runMicrotask";
+
+let _microtaskQueue: MicrotaskFn[] | undefined;
+let _microtaskTimer: ITimerHandler | undefined;
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+function _flushMicrotaskQueue(): void {
+ if (_microtaskTimer) {
+ // Cancel the timeout used to trigger the microtask queue flush, if it exists. If this function
+ // is being called as part of the timeout callback and the flush may have been called before this
+ // timeout callback was executed, so we need to check if the timer is still active before canceling it.
+ _microtaskTimer.cancel();
+ }
+
+ // Run all microtasks in the queue, if any are added while the flushing is being executed, they will be
+ // appended to the end of the queue and will be flushed in this loop as well before the function exits,
+ // this ensures that all microtasks are executed in the correct order even if new microtasks are scheduled
+ // while flushing the queue.
+ let queueIdx = 0;
+ while (_microtaskQueue && _microtaskQueue.length > queueIdx) {
+ _runMicroTask(_microtaskQueue[queueIdx++]);
+ }
+
+ // Now clear the queue to ensure that any new tasks scheduled after this point will be added to a new queue
+ // and not executed in the current flush loop.
+ _microtaskQueue = UNDEF_VALUE;
+}
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+export function _addMicrotaskToQueue(callback: () => void): void {
+ if (!_microtaskQueue) {
+ _microtaskQueue = [];
+ }
+
+ // Add the microtask callback to the queue, if the queue is currently being flushed and the callback is added
+ // after the current index, it will be executed as part of the current flush loop, otherwise it will be executed
+ // in the next flush loop when the queue is flushed again.
+ _microtaskQueue.push(callback);
+
+ if (!_microtaskTimer || !_microtaskTimer.enabled) {
+ // Hook into the scheduleTimeout callback to flush the microtask queue, this is used as a
+ // fallback when native queueMicrotask is not available.
+ _setMicrotaskCallback(_flushMicrotaskQueue);
+
+ // As there may not be any existing timers to flush the microtask queue, we need to schedule
+ // a timeout to ensure the queue is flushed even if the user does not schedule any timeouts
+ // themselves. If there is an existing timeout scheduled before the microtask callback is
+ // flushed, the microtask queue will be flushed before this timeout callback is executed
+ // as part of the timeout scheduling logic.
+ if (!_microtaskTimer) {
+ _microtaskTimer = scheduleTimeout(_flushMicrotaskQueue, 0);
+ } else {
+ // If there is already a timer scheduled, we can just ensure it is enabled to trigger the flush of the microtask queue
+ // If we called refresh() and the timer was already active, it will reset (and reorder) the timer to trigger the flush
+ // later than any existing timers, but this is necessary to ensure that the microtask queue is flushed in a future turn
+ // of the event loop and not immediately.
+ _microtaskTimer.enabled = true;
+ }
+ }
+}
+
+/**
+ * @internal
+ * Reset the timer-backed microtask queue state. Intended for tests that need a clean queue/timer instance.
+ * @since 0.15.0
+ */
+export function _resetMicrotaskQueue(): void {
+ if (_microtaskTimer) {
+ _microtaskTimer.cancel();
+ _microtaskTimer = UNDEF_VALUE;
+ }
+
+ _microtaskQueue = UNDEF_VALUE;
+}
+
diff --git a/lib/src/timer/timeout.ts b/lib/src/timer/timeout.ts
index 747ba879..e56822eb 100644
--- a/lib/src/timer/timeout.ts
+++ b/lib/src/timer/timeout.ts
@@ -15,6 +15,7 @@ import { ITimerHandler, _createTimerHandler } from "./handler";
// Package instance timeout override functions
let _setTimeoutFn: TimeoutOverrideFn | undefined;
let _clearTimeoutFn: ClearTimeoutOverrideFn | undefined;
+let _microtaskCallback: (() => void) | undefined;
function _resolveTimeoutFn(timeoutFn: TimeoutOverrideFn): TimeoutOverrideFn {
let result = isFunction(timeoutFn) ? timeoutFn : _setTimeoutFn;
@@ -53,6 +54,16 @@ function _createTimeoutWith(startTimer: boolean, overrideFn: TimeoutOverrideFn |
let timerFn = theArgs[0];
theArgs[0] = function () {
+ // Microtask fallback hook for environments without native queueMicrotask support, this allows us to
+ // simulate the running of microtasks before standard timeouts.
+ let microTasksFn = _microtaskCallback;
+ if (microTasksFn) {
+ // Run any pending microtasks before running the timeout callback to allow any microtasks
+ // scheduled within the callback to run before the next timeout.
+ _microtaskCallback = UNDEF_VALUE;
+ microTasksFn();
+ }
+
handler.dn();
fnApply(timerFn, UNDEF_VALUE, ArrSlice[CALL](arguments));
};
@@ -75,6 +86,20 @@ function _createTimeoutWith(startTimer: boolean, overrideFn: TimeoutOverrideFn |
return handler.h;
}
+/**
+ * @internal
+ * Internal function to set the microtask callback used by the microtask scheduling functions, this is used as
+ * a hook to allow the timeout implementation to run any pending microtasks before running a timeout callback
+ * @since 0.15.0
+ * @param queueFn - The function to be called to flush the microtask queue, this will be set by the microtask
+ * scheduling functions when they need to schedule a flush of the microtask queue
+ */
+export function _setMicrotaskCallback(queueFn: (() => void) | undefined): void {
+ if (!_microtaskCallback && isFunction(queueFn)) {
+ _microtaskCallback = queueFn;
+ }
+}
+
/**
* Sets the setTimeout and clearTimeout override functions for this package/closure instance to be used by all timeout operations
* when no specific override functions are provided. If called with no parameters or undefined,
diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js
index 61699374..4f71e372 100644
--- a/lib/test/bundle-size-check.js
+++ b/lib/test/bundle-size-check.js
@@ -7,25 +7,25 @@ const configs = [
{
name: "es5-min-full",
path: "../bundle/es5/umd/ts-utils.min.js",
- limit: 36 * 1024, // 36 kb in bytes
+ limit: 37 * 1024, // 37 kb in bytes
compress: false
},
{
name: "es6-min-full",
path: "../bundle/es6/umd/ts-utils.min.js",
- limit: 35.5 * 1024, // 35.5 kb in bytes
+ limit: 36 * 1024, // 36 kb in bytes
compress: false
},
{
name: "es5-min-zip",
path: "../bundle/es5/umd/ts-utils.min.js",
- limit: 14 * 1024, // 14 kb in bytes
+ limit: 14.5 * 1024, // 14.5 kb in bytes
compress: true
},
{
name: "es6-min-zip",
path: "../bundle/es6/umd/ts-utils.min.js",
- limit: 14 * 1024, // 14 kb in bytes
+ limit: 14.5 * 1024, // 14.5 kb in bytes
compress: true
},
{
diff --git a/lib/test/src/common/object/for_each_key.test.ts b/lib/test/src/common/object/for_each_key.test.ts
index ac7afa0c..43eb19c0 100644
--- a/lib/test/src/common/object/for_each_key.test.ts
+++ b/lib/test/src/common/object/for_each_key.test.ts
@@ -258,9 +258,9 @@ describe("object for_each_key tests", () => {
"constructor": "attack",
"prototype": "attack",
"name": "Dave"
- } as { [key: string]: string };
+ } as { [key: string]: string };
- Object.defineProperty(obj, "__proto__", {
+ Object.defineProperty(obj, "__proto__", {
configurable: true,
enumerable: true,
value: "attack",
diff --git a/lib/test/src/common/timer/microtask.test.ts b/lib/test/src/common/timer/microtask.test.ts
new file mode 100644
index 00000000..f70d43b2
--- /dev/null
+++ b/lib/test/src/common/timer/microtask.test.ts
@@ -0,0 +1,452 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import * as sinon from "sinon";
+import { assert } from "@nevware21/tripwire-chai";
+import { getGlobal } from "../../../../src/helpers/environment";
+import { setBypassLazyCache } from "../../../../src/helpers/lazy";
+import { getQueueMicrotask, hasQueueMicrotask, scheduleMicrotask, setMicroTaskFallbackOptions } from "../../../../src/timer/microtask";
+import { _addMicrotaskToQueue, _resetMicrotaskQueue } from "../../../../src/timer/microtasks/timerMicrotask";
+import { _runMicroTask } from "../../../../src/timer/microtasks/runMicrotask";
+import { scheduleTimeout, setTimeoutOverrides } from "../../../../src/timer/timeout";
+
+describe("microtask tests", () => {
+ let orgPromise: any;
+ let orgQueueMicrotask: any;
+ let orgSetTimeout: any;
+
+ before(() => {
+ orgPromise = (getGlobal()).Promise;
+ orgQueueMicrotask = (getGlobal()).queueMicrotask;
+ orgSetTimeout = setTimeout;
+ });
+
+ beforeEach(() => {
+ setBypassLazyCache(true);
+ setMicroTaskFallbackOptions();
+ (getGlobal()).Promise = orgPromise;
+ (getGlobal()).queueMicrotask = orgQueueMicrotask;
+ });
+
+ afterEach(() => {
+ (getGlobal()).Promise = orgPromise;
+ (getGlobal()).queueMicrotask = orgQueueMicrotask;
+ _resetMicrotaskQueue();
+ setTimeoutOverrides();
+ setMicroTaskFallbackOptions();
+ setBypassLazyCache(false);
+ });
+
+ it("hasQueueMicrotask", () => {
+ assert.equal(hasQueueMicrotask(), !!orgQueueMicrotask, "Check if we have queueMicrotask support");
+ assert.equal(!!getQueueMicrotask(), !!orgQueueMicrotask, "Check direct queueMicrotask getter");
+ });
+
+ it("surfaces exceptions thrown by a microtask", (done) => {
+ let thrownError: Error | undefined;
+ let expectedError = new Error("microtask failure");
+ let testTimeoutOverride = ((callback: () => void) => {
+ orgSetTimeout(() => {
+ try {
+ callback();
+ } catch (e) {
+ thrownError = e as Error;
+ }
+ }, 0);
+
+ return 0;
+ }) as any;
+
+ setTimeoutOverrides(testTimeoutOverride);
+
+ _runMicroTask(() => {
+ throw expectedError;
+ });
+
+ orgSetTimeout(() => {
+ assert.equal(thrownError, expectedError, "Expected the microtask error to be re-thrown on a new task");
+ done();
+ }, 10);
+ });
+
+ it("scheduleMicroTask with available microtask queue", (done) => {
+ let events: string[] = [];
+ let handler = scheduleMicrotask(() => {
+ events.push("micro");
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ events.push("sync");
+
+ orgSetTimeout(() => {
+ events.push("timeout");
+ assert.deepEqual(events, ["sync", "micro", "timeout"], "Expected microtask ordering");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ it("cancel prevents callback with available microtask queue", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.cancel();
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+
+ orgSetTimeout(() => {
+ assert.equal(called, 0, "Expected callback to not run after cancel");
+ done();
+ }, 0);
+ });
+
+ it("refresh reschedules callback with available microtask queue", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.refresh();
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Expected callback to run once after refresh");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ it("enabled=false then enabled=true re-schedules with available microtask queue", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.enabled = false;
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ handler.enabled = true;
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Expected callback to run once after enabled toggle");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ describe("without queueMicrotask", () => {
+ beforeEach(() => {
+ (getGlobal()).queueMicrotask = null;
+ });
+
+ afterEach(() => {
+ setMicroTaskFallbackOptions();
+ });
+
+ it("refresh reschedules Promise fallback callback", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.refresh();
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Expected callback to run once after refresh");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ it("enabled=false then enabled=true re-schedules Promise fallback callback", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.enabled = false;
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ handler.enabled = true;
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Expected callback to run once after enabled toggle");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ it("uses Promise fallback", (done) => {
+ let events: string[] = [];
+ let handler = scheduleMicrotask(() => {
+ events.push("micro");
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ events.push("sync");
+
+ orgSetTimeout(() => {
+ events.push("timeout");
+ assert.deepEqual(events, ["sync", "micro", "timeout"], "Expected Promise microtask ordering");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ it("cancel prevents Promise fallback callback", (done) => {
+ let called = false;
+ let handler = scheduleMicrotask(() => {
+ called = true;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.cancel();
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+
+ orgSetTimeout(() => {
+ assert.equal(called, false, "Expected callback to not run after cancel");
+ done();
+ }, 0);
+ });
+
+ it("uses timeout fallback when Promise fallback is disabled", () => {
+ let clock = sinon.useFakeTimers();
+ try {
+ setMicroTaskFallbackOptions({ useTimeout: true });
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ assert.equal(called, 0, "Callback should not be called yet");
+ clock.tick(0);
+ assert.equal(called, 1, "Callback should be called once via timeout fallback");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ } finally {
+ clock.restore();
+ }
+ });
+
+ it("supports per-call custom fallback scheduling function", (done) => {
+ let scheduleCalls = 0;
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ }, {
+ scheduleFn: (cb) => {
+ scheduleCalls++;
+ orgSetTimeout(cb, 0);
+ }
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ assert.equal(scheduleCalls, 1, "Custom fallback scheduler should be used");
+ assert.equal(called, 0, "Callback should not be called yet");
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Callback should be called once via custom fallback scheduler");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("supports global custom fallback scheduling function", (done) => {
+ let scheduleCalls = 0;
+ setMicroTaskFallbackOptions({
+ scheduleFn: (cb) => {
+ scheduleCalls++;
+ orgSetTimeout(cb, 0);
+ }
+ });
+
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ assert.equal(scheduleCalls, 1, "Global custom fallback scheduler should be used");
+ assert.equal(called, 0, "Callback should not be called yet");
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Callback should be called once via global custom fallback scheduler");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("executes microtasks before previously scheduled timers", () => {
+ let clock = sinon.useFakeTimers();
+ try {
+ let events: string[] = [];
+
+ scheduleTimeout(() => {
+ events.push("timer");
+ }, 0);
+
+ scheduleMicrotask(() => {
+ events.push("microtask");
+ }, {
+ useTimeout: true
+ });
+
+ clock.tick(0);
+
+ assert.deepEqual(events, ["microtask", "timer"], "Expected the microtask to run before the existing timer");
+ } finally {
+ clock.restore();
+ }
+ });
+
+ it("executes nested microtasks before previously scheduled timers", () => {
+ let clock = sinon.useFakeTimers();
+ try {
+ let events: string[] = [];
+
+ scheduleTimeout(() => {
+ events.push("timer");
+ }, 0);
+
+ scheduleMicrotask(() => {
+ events.push("microtask-1");
+ scheduleMicrotask(() => {
+ events.push("microtask-2");
+ }, {
+ useTimeout: true
+ });
+ }, {
+ useTimeout: true
+ });
+
+ clock.tick(0);
+
+ assert.deepEqual(events, ["microtask-1", "microtask-2", "timer"], "Expected nested microtasks to complete before the existing timer");
+ } finally {
+ clock.restore();
+ }
+ });
+ });
+
+ describe("without queueMicrotask and Promise", () => {
+ beforeEach(() => {
+ setMicroTaskFallbackOptions();
+ (getGlobal()).queueMicrotask = null;
+ (getGlobal()).Promise = null;
+ _resetMicrotaskQueue();
+ });
+
+ it("uses scheduleTimeout fallback", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ assert.equal(called, 0, "Callback should not be called yet");
+ orgSetTimeout(() => {
+ assert.equal(called, 1, "Callback should be called once");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("cancel prevents scheduleTimeout fallback callback", (done) => {
+ let called = 0;
+ let handler = scheduleMicrotask(() => {
+ called++;
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ handler.cancel();
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ orgSetTimeout(() => {
+ assert.equal(called, 0, "Callback should not be called after cancel");
+ done();
+ }, 10);
+ });
+
+ it("executes multiple microtask batches on different ticks", (done) => {
+ let flushedBatches: string[] = [];
+ let enabled = false;
+ let pendingCallback: (() => void) | undefined;
+ let fakeHandler: any;
+
+ function _fail(err: Error) {
+ done(err);
+ }
+
+ fakeHandler = {
+ cancel: () => {
+ enabled = false;
+ },
+ refresh: () => {
+ enabled = true;
+ orgSetTimeout(() => {
+ pendingCallback && pendingCallback();
+ }, 0);
+ return fakeHandler;
+ },
+ ref: () => {
+ return fakeHandler;
+ },
+ unref: () => {
+ return fakeHandler;
+ },
+ hasRef: () => {
+ return true;
+ }
+ };
+
+ Object.defineProperty(fakeHandler, "enabled", {
+ get: () => enabled,
+ set: (value: boolean) => {
+ enabled = value;
+ }
+ });
+
+ setTimeoutOverrides([
+ ((callback: () => void) => {
+ pendingCallback = callback;
+ enabled = true;
+ orgSetTimeout(() => {
+ callback();
+ }, 0);
+ return fakeHandler;
+ }) as any,
+ () => {
+ enabled = false;
+ }
+ ]);
+
+ _addMicrotaskToQueue(() => {
+ flushedBatches.push("batch-1");
+ });
+
+ orgSetTimeout(() => {
+ _addMicrotaskToQueue(() => {
+ flushedBatches.push("batch-2");
+ });
+
+ orgSetTimeout(() => {
+ try {
+ assert.deepEqual(flushedBatches, ["batch-1", "batch-2"], "Expected both microtask batches to flush in order");
+ done();
+ } catch (e) {
+ _fail(e as Error);
+ }
+ }, 10);
+ }, 10);
+ });
+ });
+});
diff --git a/package.json b/package.json
index d9c99d7b..8f8c6bb5 100644
--- a/package.json
+++ b/package.json
@@ -105,6 +105,9 @@
"idle timer",
"timer",
"interval",
+ "microtask",
+ "queueMicrotask",
+ "cancellable microtask",
"includes",
"string contains",
"html encode",