diff --git a/.size-limit.json b/.size-limit.json
index c8131b57..2b4cbd53 100644
--- a/.size-limit.json
+++ b/.size-limit.json
@@ -2,42 +2,42 @@
{
"name": "es5-full",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "33.5 kb",
+ "limit": "35 kb",
"brotli": false,
"running": false
},
{
"name": "es6-full",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "32 kb",
+ "limit": "33.5 kb",
"brotli": false,
"running": false
},
{
"name": "es5-full-brotli",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "12 kb",
+ "limit": "12.5 kb",
"brotli": true,
"running": false
},
{
"name": "es6-full-brotli",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "11.5 kb",
+ "limit": "12 kb",
"brotli": true,
"running": false
},
{
"name": "es5-zip",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "13 Kb",
+ "limit": "13.5 Kb",
"gzip": true,
"running": false
},
{
"name": "es6-zip",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "13 Kb",
+ "limit": "13.5 Kb",
"gzip": true,
"running": false
},
@@ -107,9 +107,25 @@
{
"name": "es5-scheduleMicrotask",
"path": "lib/dist/es5/mod/ts-utils.js",
- "limit": "1.5 kb",
+ "limit": "1.75 kb",
"import": "{ scheduleMicrotask }",
"gzip": true,
"running": false
+ },
+ {
+ "name": "es5-scheduleNextTick",
+ "path": "lib/dist/es5/mod/ts-utils.js",
+ "limit": "2 kb",
+ "import": "{ scheduleNextTick }",
+ "gzip": true,
+ "running": false
+ },
+ {
+ "name": "es5-scheduleNextTickAndMicrotask",
+ "path": "lib/dist/es5/mod/ts-utils.js",
+ "limit": "2 kb",
+ "import": "{ scheduleNextTick, scheduleMicrotask }",
+ "gzip": true,
+ "running": false
}
]
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30705886..359ccfce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,11 +4,17 @@
### Features
-- Add microtask scheduling helpers with native `queueMicrotask`, Promise, and timer-backed fallbacks
+- [#570](https://github.com/nevware21/ts-utils/pull/570) 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
+- [#573](https://github.com/nevware21/ts-utils/pull/573) Add nextTick scheduling support and new helper exports
+ - New timer/runtime functions: `scheduleNextTick`, `getProcessNextTick`, `hasProcessNextTick`, `setNextTickFallbackOptions`
+ - Uses native `process.nextTick` in Node runtimes when available, while providing equivalent nextTick behavior in browser and worker runtimes via Promise and timer-backed fallbacks
+ - Adds shared queue logic and callback-argument support across microtask and nextTick scheduling
+ - Adds `arrConcat` as a dedicated array helper and `fnBindArgs` as a dedicated function helper
+ - Improves function binding typing with exported `BoundFunction` and updated signatures for `fnBind` / `fnBindArgs`
# v0.14.0 May 18th, 2026
diff --git a/README.md b/README.md
index d3c5899c..03f8b8d8 100644
--- a/README.md
+++ b/README.md
@@ -115,17 +115,17 @@ 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)(); [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)();
+| 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)(); [getProcessNextTick](https://nevware21.github.io/ts-utils/typedoc/functions/getProcessNextTick.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)(); [hasProcessNextTick](https://nevware21.github.io/ts-utils/typedoc/functions/hasProcessNextTick.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)();
| |
-| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
+| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrConcat](https://nevware21.github.io/ts-utils/typedoc/functions/arrConcat.html)(); [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
| ArrayLike | [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexKeys.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrKeys](https://nevware21.github.io/ts-utils/typedoc/functions/arrKeys.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)();
| DOM | [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)();
| Enum | [createEnum](https://nevware21.github.io/ts-utils/typedoc/functions/createEnum.html)(); [createEnumKeyMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumKeyMap.html)(); [createEnumValueMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumValueMap.html)(); [createSimpleMap](https://nevware21.github.io/ts-utils/typedoc/functions/createSimpleMap.html)(); [createTypeMap](https://nevware21.github.io/ts-utils/typedoc/functions/createTypeMap.html)();
| Error | [createCustomError](https://nevware21.github.io/ts-utils/typedoc/functions/createCustomError.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [throwError](https://nevware21.github.io/ts-utils/typedoc/functions/throwError.html)(); [throwRangeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwRangeError.html)(); [throwTypeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwTypeError.html)(); [throwUnsupported](https://nevware21.github.io/ts-utils/typedoc/functions/throwUnsupported.html)();
-| Function | [fnApply](https://nevware21.github.io/ts-utils/typedoc/functions/fnApply.html)(); [fnBind](https://nevware21.github.io/ts-utils/typedoc/functions/fnBind.html)(); [fnCall](https://nevware21.github.io/ts-utils/typedoc/functions/fnCall.html)(); [createFnDeferredProxy](https://nevware21.github.io/ts-utils/typedoc/functions/createFnDeferredProxy.html)(); [createProxyFuncs](https://nevware21.github.io/ts-utils/typedoc/functions/createProxyFuncs.html)(); [readArgs](https://nevware21.github.io/ts-utils/typedoc/functions/readArgs.html)();
+| Function | [fnApply](https://nevware21.github.io/ts-utils/typedoc/functions/fnApply.html)(); [fnBind](https://nevware21.github.io/ts-utils/typedoc/functions/fnBind.html)(); [fnBindArgs](https://nevware21.github.io/ts-utils/typedoc/functions/fnBindArgs.html)(); [fnCall](https://nevware21.github.io/ts-utils/typedoc/functions/fnCall.html)(); [createFnDeferredProxy](https://nevware21.github.io/ts-utils/typedoc/functions/createFnDeferredProxy.html)(); [createProxyFuncs](https://nevware21.github.io/ts-utils/typedoc/functions/createProxyFuncs.html)(); [readArgs](https://nevware21.github.io/ts-utils/typedoc/functions/readArgs.html)();
| Idle | [getCancelIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getCancelIdleCallback.html)(); [getIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getIdleCallback.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [setDefaultIdleTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultIdleTimeout.html)(); [setDefaultMaxExecutionTime](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultMaxExecutionTime.html)();
| Iterator | [createArrayIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createArrayIterator.html)(); [createIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createIterator.html)(); [createIterable](https://nevware21.github.io/ts-utils/typedoc/functions/createIterable.html)(); [createRangeIterator](https://nevware21.github.io/ts-utils/typedoc/functions/createRangeIterator.html)(); [iterForOf](https://nevware21.github.io/ts-utils/typedoc/functions/iterForOf.html)(); [isAsyncIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncIterable.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [makeIterable](https://nevware21.github.io/ts-utils/typedoc/functions/makeIterable.html)(); [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)();
| Number | [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIntegerInRange](https://nevware21.github.io/ts-utils/typedoc/functions/isIntegerInRange.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)();
@@ -133,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)(); [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)();
+| 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)(); [setNextTickFallbackOptions](https://nevware21.github.io/ts-utils/typedoc/functions/setNextTickFallbackOptions.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)(); [scheduleNextTick](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleNextTick.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)();
@@ -309,7 +309,7 @@ Some additional polyfills are provided for simple backward compatibility to enab
### TypeScript Support
-Built with TypeScript v5.2.2, with minimal requirements of TypeScript v2.8+ for the type definitions.
+Built with TypeScript v5.2.2, while currently limiting exported types to those supported by TypeScript v3.4+.
## Size Analysis and Bundle Optimization
diff --git a/lib/package.json b/lib/package.json
index e9864043..1543cef6 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -107,7 +107,10 @@
"interval",
"microtask",
"queueMicrotask",
+ "nextTick",
+ "process-nexttick",
"cancellable microtask",
+ "function-bind",
"includes",
"string contains",
"html encode",
@@ -147,6 +150,7 @@
"array-reduce",
"array-filter",
"array-find",
+ "array-concat",
"object-copy",
"object-keys",
"lightweight",
diff --git a/lib/src/array/concat.ts b/lib/src/array/concat.ts
new file mode 100644
index 00000000..c1127069
--- /dev/null
+++ b/lib/src/array/concat.ts
@@ -0,0 +1,45 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { ArrProto } from "../internal/constants";
+import { _unwrapFunction } from "../internal/unwrapFunction";
+
+/**
+ * Alias of `Array.prototype.concat()` for consumers that prefer the `arr*` helper naming.
+ *
+ * Unlike {@link arrAppend}, this helper does NOT mutate the input arrays and returns a new array.
+ * @since 0.15.0
+ * @function
+ * @group Array
+ * @param theArray - The array to concatenate into a new array.
+ * @param items - Additional arrays and/or items to concatenate.
+ * @returns A new array containing values from `theArray` followed by each concatenated item.
+ * @example
+ * ```ts
+ * let values = arrConcat([1], [2, 3]);
+ * // values is [1, 2, 3]
+ *
+ * let next = arrConcat(values, ["a", "b"]);
+ * // next is [1, 2, 3, "a", "b"]
+ * // values is still [1, 2, 3]
+ * ```
+ * @example
+ * ```ts
+ * const left = [1, 2];
+ * const right = [3, 4];
+ * const merged = arrConcat(left, right, 5);
+ * // merged is [1, 2, 3, 4, 5]
+ * // left is still [1, 2]
+ * // right is still [3, 4]
+ * ```
+ */
+export const arrConcat: {
+ (theArray: T[]): T[];
+ (theArray: T[], ...items: ConcatArray[]): T[];
+ (theArray: T[], ...items: (T | ConcatArray)[]): T[];
+} = (/*#__PURE__*/_unwrapFunction("concat", ArrProto));
diff --git a/lib/src/funcs/fnBindArgs.ts b/lib/src/funcs/fnBindArgs.ts
new file mode 100644
index 00000000..7832c687
--- /dev/null
+++ b/lib/src/funcs/fnBindArgs.ts
@@ -0,0 +1,44 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { BoundFunction } from "./types";
+
+/**
+ * Creates a new bound function using `fn.bind()` where the pre-bound arguments are supplied as an
+ * array or tuple value, similar to how {@link fnApply} supplies arguments to invocation.
+ *
+ * This is useful when the argument list is already available as an array or tuple value,
+ * but you want to return a reusable bound function rather than invoke immediately.
+ * @param fn - The function instance to bind.
+ * @param thisArg - The value to be used as the `this` when calling `fn`.
+ * @param argArray - Optional array or tuple value containing arguments to pre-bind to `fn`.
+ * @returns A new bound function.
+ * @since 0.15.0
+ * @group Function
+ * @example
+ * ```ts
+ * const module1 = {
+ * prefix: "Hello",
+ * log(value: string, punctuation: string) {
+ * return this.prefix + " " + value + punctuation;
+ * }
+ * };
+ *
+ * const module2 = {
+ * prefix: "Hi"
+ * };
+ *
+ * const bound = fnBindArgs(module1.log, module2, ["friend"]);
+ * bound("!"); // "Hi friend!"
+ * ```
+ */
+export function fnBindArgs any, T, TArgs extends any[]>(fn: F, thisArg: T, argArray: TArgs): BoundFunction;
+export function fnBindArgs any, T>(fn: F, thisArg: T): F;
+export function fnBindArgs any, T>(fn: F, thisArg: T, argArray?: any[]): any {
+ return fn.bind.apply(fn, (argArray ? [ thisArg ].concat(argArray) : [ thisArg ]) as any);
+}
diff --git a/lib/src/funcs/funcs.ts b/lib/src/funcs/funcs.ts
index f6428630..2ea4a666 100644
--- a/lib/src/funcs/funcs.ts
+++ b/lib/src/funcs/funcs.ts
@@ -7,6 +7,7 @@
*/
import { ArrSlice, CALL } from "../internal/constants";
+import { BoundFunction } from "./types";
/**
* The `fnApply` function calls the specified `fn` function with the given `thisArg` as the `this` value,
@@ -213,7 +214,7 @@ export function fnCall any, T>(fn: F, thisArg: T): R
* module2.getX(); // 21
* ```
*/
-export function fnBind(fn: F, thisArg: T, ...argArray: any[]): F;
+export function fnBind any, T>(fn: F, thisArg: T): F;
/**
* Creates a new function that when called will set the value of `thisArg` as the `this` keyword
@@ -249,6 +250,34 @@ export function fnBind(fn: F, thisArg: T, ...argArray: an
* module2.getX(); // 21
* ```
*/
-export function fnBind(fn: F, thisArg: T): F {
- return fn.bind.apply(fn, ArrSlice[CALL](arguments, 1));
+export function fnBind any, T, TArgs extends any[]>(fn: F, thisArg: T, ...argArray: TArgs): BoundFunction;
+
+/**
+ * Fallback overload for less-specific `Function`-typed call sites where the concrete parameter
+ * list is not available, such as dynamic proxy wiring.
+ * @param fn - The function instance to be called.
+ * @param thisArg - The value to be used as the `this` when calling the `fn`.
+ * @param argArray - Zero or more arguments to pre-bind to the function.
+ * @returns The original function type with the bound `this` value and any provided arguments.
+ * @since 0.9.8
+ * @group Function
+ * @example
+ * ```ts
+ * const host = {
+ * prefix: "Hello",
+ * log(value: string) {
+ * return this.prefix + " " + value;
+ * }
+ * };
+ *
+ * const dynamicLog: Function = host.log;
+ * const bound = fnBind(dynamicLog, host);
+ *
+ * bound("friend"); // "Hello friend"
+ * ```
+ */
+export function fnBind(fn: F, thisArg: T, ...argArray: any[]): F;
+
+export function fnBind any, T>(fn: F, thisArg: T): any {
+ return fn.bind.apply(fn, ArrSlice[CALL](arguments, 1) as any);
}
diff --git a/lib/src/funcs/types.ts b/lib/src/funcs/types.ts
index 24906102..b9defd83 100644
--- a/lib/src/funcs/types.ts
+++ b/lib/src/funcs/types.ts
@@ -66,3 +66,35 @@ export type ProxyFunctionDef = {
*/
rp?: boolean
}
+
+/**
+ * Produces the return type for a function after a prefix of arguments has already been bound.
+ *
+ * This implementation is a TypeScript 3.4+ compatible middle ground between:
+ * 1. A fully simplified fallback (`(...args: any[]) => ReturnType`)
+ * 2. A TS 4.0+ variadic tuple implementation (shown below as comments)
+ *
+ * It preserves remaining argument inference for up to 5 pre-bound arguments and then
+ * falls back to a generic callable return type.
+ * @since 0.15.0
+ * @group Funcs
+ * @typeParam TFn - The function being bound.
+ * @typeParam TArgs - The tuple of arguments that will be pre-bound.
+ */
+export type BoundFunction any, TArgs extends any[]> =
+ TArgs extends [] ? TFn :
+ TArgs extends [any] ? (TFn extends (a0: any, ...args: infer R) => infer RT ? (...args: R) => RT : (...args: any[]) => ReturnType) :
+ TArgs extends [any, any] ? (TFn extends (a0: any, a1: any, ...args: infer R) => infer RT ? (...args: R) => RT : (...args: any[]) => ReturnType) :
+ TArgs extends [any, any, any] ? (TFn extends (a0: any, a1: any, a2: any, ...args: infer R) => infer RT ? (...args: R) => RT : (...args: any[]) => ReturnType) :
+ TArgs extends [any, any, any, any] ? (TFn extends (a0: any, a1: any, a2: any, a3: any, ...args: infer R) => infer RT ? (...args: R) => RT : (...args: any[]) => ReturnType) :
+ TArgs extends [any, any, any, any, any] ? (TFn extends (a0: any, a1: any, a2: any, a3: any, a4: any, ...args: infer R) => infer RT ? (...args: R) => RT : (...args: any[]) => ReturnType) :
+ (...args: any[]) => ReturnType;
+// Requires TypeScript 4.0+ for variadic tuple types (this is a better alternative for the fnBind and fnBindArgs return types)
+// /**
+// * Produces the return type for a function after a prefix of arguments has already been bound.
+// * @since 0.15.0
+// * @group Funcs
+// * @typeParam TFn - The function being bound.
+// * @typeParam TArgs - The tuple of arguments that will be pre-bound.
+// */
+// export type BoundFunction any, TArgs extends any[]> = TFn extends (...args: [...TArgs, ...infer TRemaining]) => infer TReturn ? (...args: TRemaining) => TReturn : never;
diff --git a/lib/src/helpers/environment.ts b/lib/src/helpers/environment.ts
index f56d04cd..5a9154ca 100644
--- a/lib/src/helpers/environment.ts
+++ b/lib/src/helpers/environment.ts
@@ -223,7 +223,10 @@ export const getHistory = (/*#__PURE__*/_getGlobalInstFn(getInst, ["his
* @returns True if you are
*/
export const isNode = (/*#__PURE__*/_getGlobalInstFn(() => {
- return !!safe(() => (process && (process.versions||{}).node)).v;
+ return !!safe(() => {
+ let processInst = getInst("process");
+ return processInst && (processInst.versions || {}).node;
+ }).v;
}));
/**
diff --git a/lib/src/index.ts b/lib/src/index.ts
index 064df429..50aa0c7b 100644
--- a/lib/src/index.ts
+++ b/lib/src/index.ts
@@ -9,6 +9,7 @@
///
export { arrAppend } from "./array/append";
+export { arrConcat } from "./array/concat";
export { ArrPredicateCallbackFn, ArrPredicateCallbackFn2, ArrMapCallbackFn, ArrFlatMapCallbackFn, ArrFromMapFn } from "./array/callbacks";
export { arrAt } from "./array/at";
export { arrChunk } from "./array/chunk";
@@ -46,9 +47,10 @@ export { arrUnzip } from "./array/unzip";
export { arrWith } from "./array/with";
export { arrZip } from "./array/zip";
export { fnApply, fnBind, fnCall } from "./funcs/funcs";
+export { fnBindArgs } from "./funcs/fnBindArgs";
export { createFnDeferredProxy, createProxyFuncs } from "./funcs/fnProxy";
export { readArgs } from "./funcs/readArgs";
-export { ProxyFunctionDef, TypeFuncNames } from "./funcs/types";
+export { BoundFunction, ProxyFunctionDef, TypeFuncNames } from "./funcs/types";
export { WritableArrayLike } from "./helpers/arrayLike";
export {
isTypeof, isUndefined, isNullOrUndefined, isDefined, isString, isFunction, isObject, isArray, isArrayLike, isDate, isNumber, isBoolean,
@@ -170,6 +172,7 @@ export {
hasQueueMicrotask, scheduleMicrotask, getQueueMicrotask,
ScheduleMicrotaskFn, MicroTaskOptions, setMicroTaskFallbackOptions
} from "./timer/microtask";
+export { hasProcessNextTick, scheduleNextTick, getProcessNextTick, NextTickOptions, ProcessNextTickFn, setNextTickFallbackOptions } from "./timer/nextTick";
export {
TimeoutOverrideFn, ClearTimeoutOverrideFn, TimeoutOverrideFuncs, scheduleTimeout, scheduleTimeoutWith,
createTimeout, createTimeoutWith, setTimeoutOverrides, setGlobalTimeoutOverrides
diff --git a/lib/src/timer/microtask.ts b/lib/src/timer/microtask.ts
index 00bb04dc..d4f10c55 100644
--- a/lib/src/timer/microtask.ts
+++ b/lib/src/timer/microtask.ts
@@ -6,11 +6,15 @@
* 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";
+import { ITimerHandler } from "./handler";
+import { _createCancellableTask } from "./microtasks/cancellableTask";
+import { isArray } from "../helpers/base";
+import { _resolveScheduleFn } from "./microtasks/resolveScheduleFn";
+import { _eTaskQueueType } from "./microtasks/taskQueue";
+import { _addMicrotaskQueue } from "./microtasks/timerQueue";
+import { fnBindArgs } from "../funcs/fnBindArgs";
+import { UNDEF_VALUE } from "../internal/constants";
let _defaultOptions: MicroTaskOptions | undefined;
@@ -31,8 +35,10 @@ export type MicrotaskFn = () => void;
* @group Timer
* @group Environment
* @param callback - The microtask callback function to schedule.
+ * @param maxQueuedTasks - Optional, the maximum number of queued tasks allowed before the scheduler
+ * starts dropping tasks or throwing errors, depending on the implementation.
*/
-export type ScheduleMicrotaskFn = (callback: MicrotaskFn) => void;
+export type ScheduleMicrotaskFn = (callback: MicrotaskFn, maxQueuedTasks?: number) => void | boolean;
/**
* Controls how `scheduleMicrotask` chooses fallback behavior when native
@@ -138,6 +144,7 @@ export function setMicroTaskFallbackOptions(options?: MicroTaskOptions): void {
* @group Timer
*
* @param callback - The callback to execute.
+ * @param args - Optional callback arguments to pass when the callback executes.
* @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
@@ -166,62 +173,40 @@ export function setMicroTaskFallbackOptions(options?: MicroTaskOptions): void {
* }
* });
* ```
+ * @example
+ * ```ts
+ * // Args only
+ * scheduleMicrotask((name: string, count: number) => {
+ * console.log(name, count);
+ * }, ["task", 1]);
+ *
+ * // Args + options
+ * scheduleMicrotask((name: string, count: number) => {
+ * console.log(name, count);
+ * }, ["task", 2], {
+ * useTimeout: true
+ * });
+ * ```
*/
-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();
- }
- }
+export function scheduleMicrotask(callback: () => void, options?: MicroTaskOptions): ITimerHandler;
+export function scheduleMicrotask(callback: (...args: TArgs) => void, args: TArgs, options?: MicroTaskOptions): ITimerHandler;
+export function scheduleMicrotask(callback: (...args: TArgs) => void, argsOrOptions?: TArgs | MicroTaskOptions, options?: MicroTaskOptions): ITimerHandler {
+ let callbackArgs: TArgs | undefined;
+ let theOptions: MicroTaskOptions | undefined;
+
+ if (arguments.length > 1 && argsOrOptions && isArray(argsOrOptions)) {
+ callbackArgs = argsOrOptions as TArgs;
+ theOptions = options;
+ } else {
+ theOptions = argsOrOptions as MicroTaskOptions;
}
- 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;
- }
+ let taskCallback = callbackArgs ? fnBindArgs(callback, UNDEF_VALUE, callbackArgs) : (callback as MicrotaskFn);
+ let queueMicrotaskFn = getQueueMicrotask();
- function _cancelTask(taskId: number) {
- if (taskId === currentTask) {
- currentTask++;
- }
+ if (!queueMicrotaskFn) {
+ queueMicrotaskFn = _resolveScheduleFn(theOptions, _defaultOptions, _eTaskQueueType.microtask);
}
- 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;
+ return _createCancellableTask(taskCallback, queueMicrotaskFn || _addMicrotaskQueue);
}
diff --git a/lib/src/timer/microtasks/cancellableTask.ts b/lib/src/timer/microtasks/cancellableTask.ts
new file mode 100644
index 00000000..a645cc4c
--- /dev/null
+++ b/lib/src/timer/microtasks/cancellableTask.ts
@@ -0,0 +1,47 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { ITimerHandler, _TimerHandler, _createTimerHandler } from "../handler";
+import { ScheduleMicrotaskFn } from "../microtask";
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+export function _createCancellableTask(callback: () => void, queueFn: ScheduleMicrotaskFn, maxQueuedTasks?: number): ITimerHandler {
+ let handler: _TimerHandler;
+ let currentTask = 0;
+
+ function _scheduleTask() {
+ let taskId = ++currentTask;
+ let queued = queueFn(() => {
+ if (taskId === currentTask) {
+ handler.dn();
+ callback();
+ }
+ }, maxQueuedTasks);
+
+ if (queued === false) {
+ handler.dn();
+ }
+
+ return taskId;
+ }
+
+ function _cancelTask(taskId: number) {
+ if (taskId === currentTask) {
+ currentTask++;
+ }
+ }
+
+ handler = _createTimerHandler(false, _scheduleTask, _cancelTask);
+ // Start after handler assignment in case queueFn executes synchronously.
+ handler.h.refresh();
+
+ return handler.h;
+}
diff --git a/lib/src/timer/microtasks/processNextTick.ts b/lib/src/timer/microtasks/processNextTick.ts
new file mode 100644
index 00000000..90e30246
--- /dev/null
+++ b/lib/src/timer/microtasks/processNextTick.ts
@@ -0,0 +1,57 @@
+/*
+ * @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, isNode } from "../../helpers/environment";
+import { _globalLazyTestHooks, _initTestHooks } from "../../helpers/lazy";
+import { UNDEF_VALUE } from "../../internal/constants";
+import { ScheduleMicrotaskFn } from "../microtask";
+import { _runMicroTask } from "./runMicrotask";
+
+interface IProcessLike {
+ nextTick?: (callback: () => void) => void;
+}
+
+let _nextTickFn: ICachedValue;
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+function _nextTickScheduleFn(processInst: IProcessLike): ScheduleMicrotaskFn {
+ return function(callback: () => void): void {
+ processInst.nextTick(() => {
+ _runMicroTask(callback);
+ });
+ };
+}
+
+/**
+ * @internal
+ * Resolves the process.nextTick-based scheduler from the current global state without caching.
+ * @since 0.15.0
+ */
+function _resolveProcessNextTickFn(): ScheduleMicrotaskFn | undefined {
+ let processInst = getInst("process");
+ return (isNode() && processInst && isFunction(processInst.nextTick)) ? _nextTickScheduleFn(processInst) : UNDEF_VALUE as any;
+}
+
+/**
+ * @internal
+ * Returns the process.nextTick-based scheduler, re-resolving globals when lazy bypass is active (e.g. tests).
+ * @since 0.15.0
+ */
+export function _getProcessNextTickFn(): ScheduleMicrotaskFn | undefined {
+ !_globalLazyTestHooks && _initTestHooks();
+ if (!_nextTickFn || _globalLazyTestHooks.lzy) {
+ _nextTickFn = createCachedValue(_resolveProcessNextTickFn());
+ }
+
+ return _nextTickFn.v;
+}
diff --git a/lib/src/timer/microtasks/promiseMicrotask.ts b/lib/src/timer/microtasks/promiseMicrotask.ts
deleted file mode 100644
index 348f61ae..00000000
--- a/lib/src/timer/microtasks/promiseMicrotask.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * @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/promiseQueue.ts b/lib/src/timer/microtasks/promiseQueue.ts
new file mode 100644
index 00000000..9da39afc
--- /dev/null
+++ b/lib/src/timer/microtasks/promiseQueue.ts
@@ -0,0 +1,91 @@
+/*
+ * @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 { ScheduleMicrotaskFn } from "../microtask";
+import { _addToQueue, _eTaskQueueType, _flushQueues } from "./taskQueue";
+
+let _promiseCls: ICachedValue;
+let _promisePending = false;
+let _pendingPromiseCls: PromiseConstructor | undefined;
+let _promiseQueueFns: { [key in _eTaskQueueType]?: ScheduleMicrotaskFn } | undefined;
+
+function _ensurePromiseTask(promiseCls: PromiseConstructor): void {
+ // If the Promise implementation has changed (e.g. lazy bypass tests), clear stale pending state
+ // so a fresh Promise batch can be scheduled with the current implementation.
+ if (_promisePending && _pendingPromiseCls !== promiseCls) {
+ _promisePending = false;
+ }
+
+ if (!_promisePending) {
+ _promisePending = true;
+ _pendingPromiseCls = promiseCls;
+ try {
+ promiseCls.resolve().then(() => {
+ _promisePending = false;
+ _pendingPromiseCls = undefined;
+ _flushQueues();
+ });
+ } catch (e) {
+ _promisePending = false;
+ _pendingPromiseCls = undefined;
+ _flushQueues();
+ }
+ }
+}
+
+function _promiseScheduleFn(promiseCls: PromiseConstructor, queueType: _eTaskQueueType): ScheduleMicrotaskFn {
+ return function(callback: () => void, maxQueuedTasks?: number): boolean {
+ let added = _addToQueue(queueType, callback, maxQueuedTasks);
+ if (added) {
+ _ensurePromiseTask(promiseCls);
+ }
+
+ return added;
+ };
+}
+
+/**
+ * @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 _getPromiseQueueFn(queueType: _eTaskQueueType): ScheduleMicrotaskFn | undefined {
+ !_globalLazyTestHooks && _initTestHooks();
+ if (!_promiseCls || _globalLazyTestHooks.lzy) {
+ let promClass = getInst("Promise");
+ if (promClass && isFunction(promClass.resolve)) {
+ _promiseCls = createCachedValue(promClass);
+ _promiseQueueFns = undefined;
+ } else {
+ // Clear stale Promise cache when Promise is unavailable or invalid so callers can
+ // reliably fall back to timer-backed scheduling.
+ _promiseCls = undefined;
+ _promiseQueueFns = undefined;
+ _promisePending = false;
+ _pendingPromiseCls = undefined;
+ }
+ }
+
+ let result: ScheduleMicrotaskFn | undefined;
+ if (_promiseCls && _promiseCls.v) {
+ !_promiseQueueFns && (_promiseQueueFns = {});
+ result = _promiseQueueFns[queueType];
+ if (!result) {
+ result = _promiseScheduleFn(_promiseCls.v, queueType);
+ _promiseQueueFns[queueType] = result;
+ }
+ }
+
+ return result;
+}
+
diff --git a/lib/src/timer/microtasks/resolveScheduleFn.ts b/lib/src/timer/microtasks/resolveScheduleFn.ts
new file mode 100644
index 00000000..c75f4c82
--- /dev/null
+++ b/lib/src/timer/microtasks/resolveScheduleFn.ts
@@ -0,0 +1,46 @@
+/*
+ * @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 { ScheduleMicrotaskFn } from "../microtask";
+import { _getPromiseQueueFn } from "./promiseQueue";
+import { _eTaskQueueType } from "./taskQueue";
+
+/**
+ * @internal
+ * Shared fallback scheduler options shape used by timer helpers.
+ * @since 0.15.0
+ */
+export interface _IFallbackScheduleOptions {
+ scheduleFn?: ScheduleMicrotaskFn;
+ useTimeout?: boolean;
+}
+
+/**
+ * @internal
+ * Resolve a fallback schedule function from per-call and default options.
+ * The per-call options take precedence over default options.
+ * @since 0.15.0
+ */
+export function _resolveScheduleFn(options: _IFallbackScheduleOptions | undefined, defaultOptions: _IFallbackScheduleOptions | undefined, taskQueueType: _eTaskQueueType): ScheduleMicrotaskFn | undefined {
+ let resolvedFn = (options && options.scheduleFn) || (defaultOptions && defaultOptions.scheduleFn);
+ if (!resolvedFn) {
+ // 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
+ resolvedFn = _getPromiseQueueFn(taskQueueType);
+ }
+ }
+
+ return resolvedFn;
+}
diff --git a/lib/src/timer/microtasks/runMicrotask.ts b/lib/src/timer/microtasks/runMicrotask.ts
index 5f8d7ea6..737e844b 100644
--- a/lib/src/timer/microtasks/runMicrotask.ts
+++ b/lib/src/timer/microtasks/runMicrotask.ts
@@ -20,7 +20,9 @@ import { scheduleTimeout } from "../timeout";
*/
export function _runMicroTask(callback: MicrotaskFn): void {
try {
- callback();
+ if (callback) {
+ callback();
+ }
} catch (e) {
scheduleTimeout(() => {
throw e;
diff --git a/lib/src/timer/microtasks/taskQueue.ts b/lib/src/timer/microtasks/taskQueue.ts
new file mode 100644
index 00000000..cfe7c590
--- /dev/null
+++ b/lib/src/timer/microtasks/taskQueue.ts
@@ -0,0 +1,83 @@
+/*
+ * @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 { throwRangeError } from "../../helpers/throw";
+import { UNDEF_VALUE } from "../../internal/constants";
+import { _runMicroTask } from "./runMicrotask";
+
+export const enum _eTaskQueueType {
+ microtask = 0,
+ nextTick = 1
+}
+
+let _taskQueues: { [key in _eTaskQueueType]?: (() => void)[] } | undefined;
+
+export function _flushQueues(): void {
+ if (_taskQueues) {
+ _taskQueues[_eTaskQueueType.nextTick] && _flushTaskQueue(_eTaskQueueType.nextTick);
+ _taskQueues[_eTaskQueueType.microtask] && _flushTaskQueue(_eTaskQueueType.microtask);
+ }
+}
+
+export function _flushTaskQueue(queueType: _eTaskQueueType): void {
+ if (_taskQueues) {
+ let queue = _taskQueues[queueType];
+ let queueIdx = 0;
+ while (queue && queue.length > queueIdx) {
+ _runMicroTask(queue[queueIdx++]);
+
+ // Special case for microtask queue to ensure nextTick queue is flushed between each microtask callback
+ if (queueType === _eTaskQueueType.microtask && _taskQueues[_eTaskQueueType.nextTick]) {
+ _flushTaskQueue(_eTaskQueueType.nextTick);
+ }
+ }
+
+ _taskQueues[queueType] = UNDEF_VALUE;
+ }
+}
+
+
+export function _addToQueue(queueType: _eTaskQueueType, callback: () => void, maxQueuedTasks?: number): boolean {
+ let result = false;
+
+ if (!_taskQueues) {
+ _taskQueues = {};
+ }
+
+ let queue = _taskQueues[queueType];
+ if (!queue) {
+ queue = [];
+ _taskQueues[queueType] = queue;
+ }
+
+ if (isStrictUndefined(maxQueuedTasks) || maxQueuedTasks === 0 || (queue.length < maxQueuedTasks)) {
+ queue.push(callback);
+ result = true;
+ } else {
+ throwRangeError(
+ "schedule" + (queueType === _eTaskQueueType.microtask ? "Microtask" : "NextTick") + "() queue depth exceeded [" + maxQueuedTasks + "]"
+ );
+ }
+
+ return result;
+}
+
+
+/**
+ * @internal
+ * Reset helper for tests.
+ * @since 0.15.0
+ */
+export function _clearTaskQueues(): void {
+ if (_taskQueues) {
+ _taskQueues[_eTaskQueueType.nextTick] = UNDEF_VALUE;
+ _taskQueues[_eTaskQueueType.microtask] = UNDEF_VALUE;
+ _taskQueues = UNDEF_VALUE;
+ }
+}
\ No newline at end of file
diff --git a/lib/src/timer/microtasks/timerMicrotask.ts b/lib/src/timer/microtasks/timerMicrotask.ts
deleted file mode 100644
index 33fb7240..00000000
--- a/lib/src/timer/microtasks/timerMicrotask.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * @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/microtasks/timerQueue.ts b/lib/src/timer/microtasks/timerQueue.ts
new file mode 100644
index 00000000..c7186a63
--- /dev/null
+++ b/lib/src/timer/microtasks/timerQueue.ts
@@ -0,0 +1,85 @@
+/*
+ * @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 { _returnNothing } from "../../internal/stubs";
+import { ITimerHandler } from "../handler";
+import { _setMicrotaskCallback, scheduleTimeout } from "../timeout";
+import { _addToQueue, _eTaskQueueType, _flushQueues } from "./taskQueue";
+
+let _sharedTimer: ITimerHandler | undefined;
+let _isFlushing = false;
+
+function _doFlush() {
+ try {
+ _isFlushing = true;
+ _flushQueues();
+ } finally {
+ _isFlushing = false;
+ }
+}
+
+/**
+ * @internal
+ * Internal function to ensure the shared timer is set. If the timer is already set and not enabled it will be refreshed
+ * to ensure it will pick up the new queue items when it flushes.
+ */
+function _ensureSharedTimer(): void {
+ if (_isFlushing) {
+ return;
+ }
+
+ _setMicrotaskCallback(_doFlush);
+
+ let handler = _sharedTimer;
+ if (!handler) {
+ _sharedTimer = scheduleTimeout(_returnNothing, 0);
+ } else if (!handler.enabled) {
+ // Restart the timer if it's not already running, otherwise just let the current timer run
+ // and it will pick up the new queue items when it flushes
+ handler.refresh();
+ }
+}
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+export function _addNextTickToQueue(callback: () => void, maxQueuedTasks?: number): boolean {
+ return _addToQueueWithTimer(_eTaskQueueType.nextTick, callback, maxQueuedTasks);
+}
+
+/**
+ * @internal
+ * @since 0.15.0
+ */
+export function _addMicrotaskQueue(callback: () => void, maxQueuedTasks?: number): boolean {
+ return _addToQueueWithTimer(_eTaskQueueType.microtask, callback, maxQueuedTasks);
+}
+
+function _addToQueueWithTimer(queueType: _eTaskQueueType, callback: () => void, maxQueuedTasks?: number): boolean {
+ let result = _addToQueue(queueType, callback, maxQueuedTasks);
+ if (result) {
+ _ensureSharedTimer();
+ }
+
+ return result;
+}
+
+
+/**
+ * @internal
+ * Resets the internal shared timer. This is primarily intended for testing purposes to ensure a clean state between tests.
+ * @since 0.15.0
+ */
+export function _resetSharedTimer(): void {
+ if (_sharedTimer) {
+ _sharedTimer.cancel();
+ _sharedTimer = UNDEF_VALUE;
+ }
+}
diff --git a/lib/src/timer/nextTick.ts b/lib/src/timer/nextTick.ts
new file mode 100644
index 00000000..494d8d4e
--- /dev/null
+++ b/lib/src/timer/nextTick.ts
@@ -0,0 +1,209 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { isArray, isFunction, isStrictUndefined } from "../helpers/base";
+import { _getGlobalInstFn, getInst, isNode } from "../helpers/environment";
+import { ITimerHandler } from "./handler";
+import { _createCancellableTask } from "./microtasks/cancellableTask";
+import { MicrotaskFn, ScheduleMicrotaskFn } from "./microtask";
+import { _getProcessNextTickFn } from "./microtasks/processNextTick";
+import { _resolveScheduleFn } from "./microtasks/resolveScheduleFn";
+import { _addNextTickToQueue } from "./microtasks/timerQueue";
+import { _eTaskQueueType } from "./microtasks/taskQueue";
+import { fnBindArgs } from "../funcs/fnBindArgs";
+import { UNDEF_VALUE } from "../internal/constants";
+
+const _defaultMaxQueuedTasks = 1000;
+
+let _defaultOptions: NextTickOptions | undefined;
+
+/**
+ * Controls how `scheduleNextTick` chooses fallback behavior when native `process.nextTick` is not available.
+ *
+ * @since 0.15.0
+ * @group Timer
+ */
+export interface NextTickOptions {
+ /**
+ * Provide a custom scheduling function to use when native `process.nextTick` is unavailable.
+ * When specified, this takes precedence over both the Promise fallback and the timer-backed
+ * queue fallback.
+ */
+ scheduleFn?: ScheduleMicrotaskFn;
+
+ /**
+ * When `true`, skips the Promise fallback and uses the timer-backed queue instead.
+ * When omitted or `false`, `scheduleNextTick` prefers the Promise fallback before the timer fallback.
+ */
+ useTimeout?: boolean;
+
+ /**
+ * The maximum number of queued nextTick callbacks that may be pending at once when using the fallback queues.
+ * When the limit is reached, scheduling throws a RangeError.
+ */
+ maxQueuedTasks?: number;
+}
+
+/**
+ * Node-compatible `process.nextTick` function signature.
+ *
+ * Unlike {@link ScheduleMicrotaskFn}, this supports variadic callback arguments and always returns `void`.
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ */
+export type ProcessNextTickFn = (callback: (...args: any[]) => void, ...args: any[]) => void;
+
+/**
+ * Sets the default fallback behavior for {@link scheduleNextTick} when
+ * `process.nextTick` is not available.
+ *
+ * @since 0.15.0
+ * @group Timer
+ *
+ * @param options - The fallback options to apply. Passing `undefined` resets
+ * options to defaults.
+ * @example
+ * ```ts
+ * setNextTickFallbackOptions({
+ * useTimeout: true
+ * });
+ *
+ * scheduleNextTick(() => {
+ * console.log("timer-backed nextTick fallback");
+ * });
+ *
+ * setNextTickFallbackOptions();
+ * ```
+ */
+export function setNextTickFallbackOptions(options?: NextTickOptions): void {
+ _defaultOptions = options;
+}
+
+/**
+ * Returns the global `process.nextTick` function if available, otherwise `null`.
+ *
+ * The returned function matches the Node.js signature and supports variadic callback arguments.
+ *
+ * @function
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ * @example
+ * ```ts
+ * const nextTickFn = getProcessNextTick();
+ * if (nextTickFn) {
+ * nextTickFn((value: string) => {
+ * console.log("process.nextTick", value);
+ * }, "works-with-args");
+ * }
+ * ```
+ */
+export const getProcessNextTick = (/*#__PURE__*/_getGlobalInstFn(() => {
+ let processInst = getInst("process");
+ return isNode() && processInst && isFunction(processInst.nextTick) ? processInst.nextTick as ProcessNextTickFn : null;
+}));
+
+/**
+ * Identifies if the runtime supports the `process.nextTick` API.
+ *
+ * @since 0.15.0
+ * @group Timer
+ * @group Environment
+ * @returns True if the runtime supports `process.nextTick` otherwise false.
+ * @example
+ * ```ts
+ * if (hasProcessNextTick()) {
+ * console.log("process.nextTick is available");
+ * }
+ * ```
+ */
+/*#__NO_SIDE_EFFECTS__*/
+export function hasProcessNextTick(): boolean {
+ return !!getProcessNextTick();
+}
+
+/**
+ * Schedules a callback to run using `process.nextTick` when available and otherwise uses an
+ * Promise-based fallback before using the timer-backed queue.
+ *
+ * When `scheduleFn` is provided, it is used before the Promise and timer-backed fallbacks.
+ * When `useTimeout` is `true`, the timer-backed queue is used instead of the Promise fallback.
+ * `maxQueuedTasks` controls the fallback queue depth limit; when omitted the Promise/timer fallback queues
+ * use a Node-compatible default of 1000. Set `maxQueuedTasks` to `0` to disable the limit (for fallbacks only,
+ * the native `process.nextTick` is not affected by this limit).
+ *
+ * @since 0.15.0
+ * @group Timer
+ *
+ * @param callback - The callback to execute.
+ * @param args - Optional callback arguments to pass when the callback executes.
+ * @param options - Optional fallback behavior when native `process.nextTick` is not available.
+ * @returns A handler that can be used to cancel or refresh the scheduled callback.
+ * @example
+ * ```ts
+ * scheduleNextTick(() => {
+ * console.log("nextTick callback");
+ * });
+ *
+ * scheduleNextTick(() => {
+ * console.log("timer-backed nextTick");
+ * }, {
+ * useTimeout: true
+ * });
+ *
+ * scheduleNextTick(() => {
+ * console.log("custom nextTick fallback");
+ * }, {
+ * scheduleFn: (cb) => {
+ * setTimeout(cb, 0);
+ * }
+ * });
+ * ```
+ * @example
+ * ```ts
+ * // Args only
+ * scheduleNextTick((name: string, count: number) => {
+ * console.log(name, count);
+ * }, ["task", 1]);
+ *
+ * // Args + options
+ * scheduleNextTick((name: string, count: number) => {
+ * console.log(name, count);
+ * }, ["task", 2], {
+ * useTimeout: true
+ * });
+ * ```
+ */
+export function scheduleNextTick(callback: MicrotaskFn, options?: NextTickOptions): ITimerHandler;
+export function scheduleNextTick(callback: (...args: TArgs) => void, args: TArgs, options?: NextTickOptions): ITimerHandler;
+export function scheduleNextTick(callback: (...args: TArgs) => void, argsOrOptions?: TArgs | NextTickOptions, options?: NextTickOptions): ITimerHandler {
+ let callbackArgs: TArgs | undefined;
+ let theOptions: NextTickOptions | undefined;
+
+ if (arguments.length > 1 && argsOrOptions && isArray(argsOrOptions)) {
+ callbackArgs = argsOrOptions as TArgs;
+ theOptions = options;
+ } else {
+ theOptions = argsOrOptions as NextTickOptions;
+ }
+
+ let taskCallback = callbackArgs ? fnBindArgs(callback, UNDEF_VALUE, callbackArgs) : (callback as MicrotaskFn);
+ let nextTickFn = _getProcessNextTickFn();
+ let maxQueuedTasks = (theOptions && !isStrictUndefined(theOptions.maxQueuedTasks))
+ ? theOptions.maxQueuedTasks
+ : ((_defaultOptions && !isStrictUndefined(_defaultOptions.maxQueuedTasks))
+ ? _defaultOptions.maxQueuedTasks
+ : _defaultMaxQueuedTasks);
+
+ if (!nextTickFn) {
+ nextTickFn = _resolveScheduleFn(theOptions, _defaultOptions, _eTaskQueueType.nextTick);
+ }
+
+ return _createCancellableTask(taskCallback, nextTickFn || _addNextTickToQueue, maxQueuedTasks);
+}
diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js
index 4f71e372..2fd2fc86 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: 37 * 1024, // 37 kb in bytes
+ limit: 38.5 * 1024, // 38.5 kb in bytes
compress: false
},
{
name: "es6-min-full",
path: "../bundle/es6/umd/ts-utils.min.js",
- limit: 36 * 1024, // 36 kb in bytes
+ limit: 37.5 * 1024, // 37.5 kb in bytes
compress: false
},
{
name: "es5-min-zip",
path: "../bundle/es5/umd/ts-utils.min.js",
- limit: 14.5 * 1024, // 14.5 kb in bytes
+ limit: 15 * 1024, // 15 kb in bytes
compress: true
},
{
name: "es6-min-zip",
path: "../bundle/es6/umd/ts-utils.min.js",
- limit: 14.5 * 1024, // 14.5 kb in bytes
+ limit: 15 * 1024, // 15 kb in bytes
compress: true
},
{
diff --git a/lib/test/src/common/array/concat.test.ts b/lib/test/src/common/array/concat.test.ts
new file mode 100644
index 00000000..b1a4c69b
--- /dev/null
+++ b/lib/test/src/common/array/concat.test.ts
@@ -0,0 +1,41 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { assert } from "@nevware21/tripwire-chai";
+import { arrConcat } from "../../../../src/array/concat";
+import { createArrayIterator } from "../../../../src/iterator/array";
+
+describe("arrConcat helper", () => {
+ it("arrConcat returns a new concatenated array", () => {
+ let target = [1];
+ let result = arrConcat(target, [2, 3]);
+
+ assert.notStrictEqual(result, target, "arrConcat should return a new array instance");
+ assert.deepEqual(result, [1, 2, 3]);
+ assert.deepEqual(target, [1], "arrConcat should not modify target");
+ });
+
+ it("arrConcat does not modify source arrays", () => {
+ let left = [1, 2];
+ let right = [3, 4];
+ let result = arrConcat(left, right);
+
+ assert.deepEqual(result, [1, 2, 3, 4]);
+ assert.deepEqual(left, [1, 2], "left array should not be modified");
+ assert.deepEqual(right, [3, 4], "right array should not be modified");
+ });
+
+ it("arrConcat keeps non-array values as a single element", () => {
+ let target = [1];
+ let iter = createArrayIterator([2, 3]);
+ let result = arrConcat(target, iter as any);
+
+ assert.deepEqual(result, [1, iter], "concat should keep non-array values as a single element");
+ assert.deepEqual(target, [1], "target should not be modified");
+ });
+});
diff --git a/lib/test/src/common/funcs/funcs.test.ts b/lib/test/src/common/funcs/funcs.test.ts
index 9d7bbf00..7d852eb9 100644
--- a/lib/test/src/common/funcs/funcs.test.ts
+++ b/lib/test/src/common/funcs/funcs.test.ts
@@ -8,6 +8,7 @@
import { assert } from "@nevware21/tripwire-chai";
import { fnApply, fnBind, fnCall } from "../../../../src/funcs/funcs";
+import { fnBindArgs } from "../../../../src/funcs/fnBindArgs";
describe("function helpers", () => {
describe("fnBind", () => {
@@ -34,6 +35,22 @@ describe("function helpers", () => {
module2.getX = fnBind(module1.getX, module1);
assert.equal(module2.getX(), 21);
});
+
+ it("binds this and pre-bound arguments", () => {
+ const module1 = {
+ prefix: "Hello",
+ log(value: string, punctuation: string) {
+ return this.prefix + " " + value + punctuation;
+ }
+ };
+
+ const module2 = {
+ prefix: "my"
+ };
+
+ const bound = fnBind(module1.log, module2, "friend");
+ assert.equal(bound("!"), "my friend!", "pre-bound arguments should be preserved");
+ });
});
describe("fnApply", () => {
@@ -68,6 +85,26 @@ describe("function helpers", () => {
});
});
+ describe("fnBindArgs", () => {
+ it("binds this and pre-bound arguments from an array", () => {
+ const module1 = {
+ prefix: "Hello",
+ x: 21,
+ log(value: string, punctuation: string) {
+ return this.prefix + " " + value + punctuation + " : " + this.x;
+ }
+ };
+
+ let module2 = {
+ prefix: "my",
+ x: 42
+ };
+
+ let bound = fnBindArgs(module1.log, module2, [ "friend" ]);
+ assert.equal(bound("!"), "my friend! : 42");
+ });
+ });
+
describe("fnCall", () => {
it("examples", () => {
assert.equal(fnCall(Math.max, null, 21, 42, 84, 168, 7, 3), 168);
diff --git a/lib/test/src/common/helpers/array.test.ts b/lib/test/src/common/helpers/array.test.ts
index 1aa33d23..f165f7f1 100644
--- a/lib/test/src/common/helpers/array.test.ts
+++ b/lib/test/src/common/helpers/array.test.ts
@@ -195,6 +195,14 @@ describe("array helpers", () => {
});
describe("arrAppend", () => {
+ it("returns the same array instance", () => {
+ let target = [1];
+ let result = arrAppend(target, [2, 3]);
+
+ assert.strictEqual(result, target, "arrAppend should return the same array instance");
+ assert.deepEqual(target, [1, 2, 3]);
+ });
+
it("Validate passing null values doesn't throw", () => {
let target = arrAppend(null, null);
assert.ok(true, "Passing null values");
diff --git a/lib/test/src/common/timer/microtask.test.ts b/lib/test/src/common/timer/microtask.test.ts
index f70d43b2..095d2905 100644
--- a/lib/test/src/common/timer/microtask.test.ts
+++ b/lib/test/src/common/timer/microtask.test.ts
@@ -11,9 +11,10 @@ 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 { _addMicrotaskQueue, _resetSharedTimer } from "../../../../src/timer/microtasks/timerQueue";
import { _runMicroTask } from "../../../../src/timer/microtasks/runMicrotask";
import { scheduleTimeout, setTimeoutOverrides } from "../../../../src/timer/timeout";
+import { _clearTaskQueues } from "../../../../src/timer/microtasks/taskQueue";
describe("microtask tests", () => {
let orgPromise: any;
@@ -36,7 +37,8 @@ describe("microtask tests", () => {
afterEach(() => {
(getGlobal()).Promise = orgPromise;
(getGlobal()).queueMicrotask = orgQueueMicrotask;
- _resetMicrotaskQueue();
+ _clearTaskQueues();
+ _resetSharedTimer();
setTimeoutOverrides();
setMicroTaskFallbackOptions();
setBypassLazyCache(false);
@@ -124,6 +126,52 @@ describe("microtask tests", () => {
}, 0);
});
+ it("supports callback arguments with available microtask queue", (done) => {
+ let calledName: string;
+ let calledCount: number;
+
+ let handler = scheduleMicrotask((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["micro", 42]);
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(calledName, "micro", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 42, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 0);
+ });
+
+ it("forwards callback arguments via native queueMicrotask implementation", (done) => {
+ let theGlobal: any = getGlobal();
+ let queueCalls = 0;
+ let calledName: string;
+ let calledCount: number;
+
+ theGlobal.queueMicrotask = (callback: () => void) => {
+ queueCalls++;
+ orgSetTimeout(callback, 0);
+ };
+
+ let handler = scheduleMicrotask((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["native", 101]);
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(queueCalls, 1, "Expected native queueMicrotask implementation to be used once");
+ assert.equal(calledName, "native", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 101, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
it("enabled=false then enabled=true re-schedules with available microtask queue", (done) => {
let called = 0;
let handler = scheduleMicrotask(() => {
@@ -221,6 +269,25 @@ describe("microtask tests", () => {
}, 0);
});
+ it("supports callback arguments via Promise fallback", (done) => {
+ let calledName: string;
+ let calledCount: number;
+
+ let handler = scheduleMicrotask((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["promise", 6]);
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(calledName, "promise", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 6, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
it("uses timeout fallback when Promise fallback is disabled", () => {
let clock = sinon.useFakeTimers();
try {
@@ -262,6 +329,53 @@ describe("microtask tests", () => {
}, 10);
});
+ it("supports callback arguments via custom fallback scheduling function", (done) => {
+ let scheduleCalls = 0;
+ let calledName: string;
+ let calledCount: number;
+
+ let handler = scheduleMicrotask((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["custom", 9], {
+ scheduleFn: (cb) => {
+ scheduleCalls++;
+ orgSetTimeout(cb, 0);
+ }
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(scheduleCalls, 1, "Custom fallback scheduler should be used");
+ assert.equal(calledName, "custom", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 9, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("supports callback arguments with options in fallback mode", (done) => {
+ let calledName: string;
+ let calledCount: number;
+
+ let handler = scheduleMicrotask((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["fallback", 7], {
+ useTimeout: true
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(calledName, "fallback", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 7, "Expected callback argument 'count' to be passed");
+ 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({
@@ -343,7 +457,8 @@ describe("microtask tests", () => {
setMicroTaskFallbackOptions();
(getGlobal()).queueMicrotask = null;
(getGlobal()).Promise = null;
- _resetMicrotaskQueue();
+ _clearTaskQueues();
+ _resetSharedTimer();
});
it("uses scheduleTimeout fallback", (done) => {
@@ -429,12 +544,12 @@ describe("microtask tests", () => {
}
]);
- _addMicrotaskToQueue(() => {
+ _addMicrotaskQueue(() => {
flushedBatches.push("batch-1");
});
orgSetTimeout(() => {
- _addMicrotaskToQueue(() => {
+ _addMicrotaskQueue(() => {
flushedBatches.push("batch-2");
});
diff --git a/lib/test/src/common/timer/nextTick.test.ts b/lib/test/src/common/timer/nextTick.test.ts
new file mode 100644
index 00000000..c9e39786
--- /dev/null
+++ b/lib/test/src/common/timer/nextTick.test.ts
@@ -0,0 +1,730 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { assert } from "@nevware21/tripwire-chai";
+import { getGlobal, isNode } from "../../../../src/helpers/environment";
+import { setBypassLazyCache } from "../../../../src/helpers/lazy";
+import { scheduleMicrotask, setMicroTaskFallbackOptions } from "../../../../src/timer/microtask";
+import { getProcessNextTick, hasProcessNextTick, scheduleNextTick, setNextTickFallbackOptions } from "../../../../src/timer/nextTick";
+import { scheduleTimeout } from "../../../../src/timer/timeout";
+import { _clearTaskQueues } from "../../../../src/timer/microtasks/taskQueue";
+import { _resetSharedTimer } from "../../../../src/timer/microtasks/timerQueue";
+
+describe("nextTick tests", () => {
+ let orgProcess: any;
+ let orgPromise: any;
+ let orgQueueMicrotask: any;
+ let orgSetTimeout: any;
+
+ before(() => {
+ let theGlobal: any = getGlobal();
+ orgProcess = theGlobal.process;
+ orgPromise = theGlobal.Promise;
+ orgQueueMicrotask = theGlobal.queueMicrotask;
+ orgSetTimeout = setTimeout;
+ });
+
+ beforeEach(() => {
+ let theGlobal: any = getGlobal();
+ setBypassLazyCache(true);
+ setMicroTaskFallbackOptions();
+ setNextTickFallbackOptions();
+ theGlobal.process = orgProcess;
+ theGlobal.Promise = orgPromise;
+ theGlobal.queueMicrotask = orgQueueMicrotask;
+ });
+
+ afterEach(() => {
+ let theGlobal: any = getGlobal();
+ theGlobal.process = orgProcess;
+ theGlobal.Promise = orgPromise;
+ theGlobal.queueMicrotask = orgQueueMicrotask;
+ _clearTaskQueues();
+ _resetSharedTimer();
+ setMicroTaskFallbackOptions();
+ setNextTickFallbackOptions();
+ setBypassLazyCache(false);
+ });
+
+ it("hasProcessNextTick", () => {
+ let hasNativeNextTick = !!(isNode() && orgProcess && orgProcess.nextTick);
+ assert.equal(hasProcessNextTick(), hasNativeNextTick, "Check if we have process.nextTick support");
+ assert.equal(!!getProcessNextTick(), hasNativeNextTick, "Check direct process.nextTick getter");
+ });
+
+ it("uses process.nextTick when available in Node runtimes", (done) => {
+ let calls = 0;
+ let theGlobal: any = getGlobal();
+ let processInst: any = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions
+ };
+ theGlobal.process = processInst;
+
+ processInst.nextTick = (callback: () => void) => {
+ calls++;
+ orgSetTimeout(callback, 0);
+ };
+
+ let handler = scheduleNextTick(() => {
+ // no-op
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ orgSetTimeout(() => {
+ assert.equal(calls, isNode() ? 1 : 0, "Expected process.nextTick usage only in Node runtimes");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("cancel prevents callback with native process.nextTick", (done) => {
+ if (!isNode()) {
+ done();
+ return;
+ }
+
+ let theGlobal: any = getGlobal();
+ let called = 0;
+ let processCalls = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: (callback: () => void) => {
+ processCalls++;
+ orgSetTimeout(callback, 0);
+ }
+ };
+
+ let handler = scheduleNextTick(() => {
+ 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(processCalls, 1, "Expected process.nextTick to be called once");
+ assert.equal(called, 0, "Expected callback to not run after cancel");
+ done();
+ }, 10);
+ });
+
+ it("refresh reschedules callback with native process.nextTick", (done) => {
+ if (!isNode()) {
+ done();
+ return;
+ }
+
+ let theGlobal: any = getGlobal();
+ let called = 0;
+ let processCalls = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: (callback: () => void) => {
+ processCalls++;
+ orgSetTimeout(callback, 0);
+ }
+ };
+
+ let handler = scheduleNextTick(() => {
+ 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(processCalls, 2, "Expected refresh to enqueue a replacement process.nextTick callback");
+ assert.equal(called, 1, "Expected callback to run once after refresh");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("supports callback arguments with native/fallback nextTick", (done) => {
+ let calledName: string;
+ let calledCount: number;
+
+ let handler = scheduleNextTick((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["nextTick", 21]);
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ orgSetTimeout(() => {
+ assert.equal(calledName, "nextTick", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 21, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("forwards callback arguments via native process.nextTick implementation", (done) => {
+ let theGlobal: any = getGlobal();
+ let processCalls = 0;
+ let calledName: string;
+ let calledCount: number;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: (callback: () => void) => {
+ processCalls++;
+ orgSetTimeout(callback, 0);
+ }
+ };
+
+ let handler = scheduleNextTick((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["native", 33]);
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(processCalls, isNode() ? 1 : 0, "Expected process.nextTick usage only in Node runtimes");
+ assert.equal(calledName, "native", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 33, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("executes nextTick before a previously scheduled native setTimeout", (done) => {
+ let events: string[] = [];
+
+ orgSetTimeout(() => {
+ events.push("timer");
+ }, 0);
+
+ scheduleNextTick(() => {
+ events.push("nextTick");
+ });
+
+ orgSetTimeout(() => {
+ assert.deepEqual(events, ["nextTick", "timer"], "Expected nextTick before a previously scheduled native setTimeout");
+ done();
+ }, 10);
+ });
+
+ it("supports custom fallback scheduling function", (done) => {
+ let theGlobal: any = getGlobal();
+ let scheduleCalls = 0;
+ let called = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick(() => {
+ 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 callback arguments via Promise fallback", (done) => {
+ let theGlobal: any = getGlobal();
+ let calledName: string;
+ let calledCount: number;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["promise", 5]);
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(calledName, "promise", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 5, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("cancel prevents callback via Promise fallback", (done) => {
+ let theGlobal: any = getGlobal();
+ let called = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick(() => {
+ 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();
+ }, 10);
+ });
+
+ it("refresh reschedules callback via Promise fallback", (done) => {
+ let theGlobal: any = getGlobal();
+ let called = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick(() => {
+ 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();
+ }, 10);
+ });
+
+ it("cancel prevents callback via timer-queue fallback", (done) => {
+ let theGlobal: any = getGlobal();
+ let called = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick(() => {
+ called++;
+ }, {
+ useTimeout: 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, 0, "Expected callback to not run after cancel");
+ done();
+ }, 10);
+ });
+
+ it("refresh reschedules callback via timer-queue fallback", (done) => {
+ let theGlobal: any = getGlobal();
+ let called = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick(() => {
+ called++;
+ }, {
+ useTimeout: true
+ });
+
+ 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();
+ }, 10);
+ });
+
+ it("supports callback arguments via custom fallback scheduling function", (done) => {
+ let theGlobal: any = getGlobal();
+ let scheduleCalls = 0;
+ let calledName: string;
+ let calledCount: number;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["custom", 12], {
+ scheduleFn: (cb) => {
+ scheduleCalls++;
+ orgSetTimeout(cb, 0);
+ }
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+
+ orgSetTimeout(() => {
+ assert.equal(scheduleCalls, 1, "Custom fallback scheduler should be used");
+ assert.equal(calledName, "custom", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 12, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("supports callback arguments with options in fallback mode", (done) => {
+ let theGlobal: any = getGlobal();
+ let calledName: string;
+ let calledCount: number;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ let handler = scheduleNextTick((name: string, count: number) => {
+ calledName = name;
+ calledCount = count;
+ }, ["fallback", 8], {
+ useTimeout: true
+ });
+
+ assert.equal(handler.enabled, true, "Check that the handler is running");
+ orgSetTimeout(() => {
+ assert.equal(calledName, "fallback", "Expected callback argument 'name' to be passed");
+ assert.equal(calledCount, 8, "Expected callback argument 'count' to be passed");
+ assert.equal(handler.enabled, false, "Check that the handler is stopped");
+ done();
+ }, 10);
+ });
+
+ it("supports global custom fallback scheduling function", (done) => {
+ let theGlobal: any = getGlobal();
+ let scheduleCalls = 0;
+ let called = 0;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ setNextTickFallbackOptions({
+ scheduleFn: (cb) => {
+ scheduleCalls++;
+ orgSetTimeout(cb, 0);
+ }
+ });
+
+ let handler = scheduleNextTick(() => {
+ 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 nextTick scheduled from first microtask before remaining microtasks and before timer", (done) => {
+ let theGlobal: any = getGlobal();
+ let events: string[] = [];
+
+ // Force the timer-backed fallback path for both microtask and nextTick behavior.
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+ theGlobal.queueMicrotask = null;
+ theGlobal.Promise = null;
+
+ scheduleMicrotask(() => {
+ events.push("microtask-1");
+ scheduleNextTick(() => {
+ events.push("nextTick");
+ });
+ });
+
+ scheduleMicrotask(() => {
+ events.push("microtask-2");
+ });
+
+ scheduleTimeout(() => {
+ events.push("timer");
+ }, 0);
+
+ orgSetTimeout(() => {
+ try {
+ assert.deepEqual(events, ["microtask-1", "nextTick", "microtask-2", "timer"], "Expected nested fallback order microtask-1 -> nextTick -> microtask-2 -> timer");
+ done();
+ } catch (e) {
+ done(e as Error);
+ }
+ }, 20);
+ });
+
+ it("uses a single Promise task for mixed nextTick and microtask fallback queues", (done) => {
+ let theGlobal: any = getGlobal();
+ let events: string[] = [];
+ let resolveCalls = 0;
+ let orgPromiseResolve = orgPromise && orgPromise.resolve;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+ theGlobal.queueMicrotask = null;
+
+ if (orgPromise && orgPromiseResolve) {
+ theGlobal.Promise = orgPromise;
+ theGlobal.Promise.resolve = function() {
+ resolveCalls++;
+ return orgPromiseResolve.call(orgPromise);
+ };
+ }
+
+ scheduleNextTick(() => {
+ events.push("nextTick");
+ });
+
+ scheduleMicrotask(() => {
+ events.push("microtask");
+ });
+
+ orgSetTimeout(() => {
+ try {
+ assert.deepEqual(events, ["nextTick", "microtask"], "Expected nextTick queue to flush before microtask queue");
+ assert.equal(resolveCalls, 1, "Expected a single Promise.resolve() call for the mixed Promise fallback batch");
+ done();
+ } catch (e) {
+ done(e as Error);
+ } finally {
+ if (orgPromise && orgPromiseResolve) {
+ orgPromise.resolve = orgPromiseResolve;
+ }
+ }
+ }, 10);
+ });
+
+ it("does not use stale Promise cache after Promise becomes unavailable", (done) => {
+ let theGlobal: any = getGlobal();
+ let promiseResolveCalls = 0;
+ let orgPromiseResolve = orgPromise && orgPromise.resolve;
+ let called = 0;
+
+ if (!orgPromise || !orgPromiseResolve) {
+ done();
+ return;
+ }
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+ theGlobal.queueMicrotask = null;
+
+ theGlobal.Promise = orgPromise;
+ theGlobal.Promise.resolve = function() {
+ promiseResolveCalls++;
+ return orgPromiseResolve.call(orgPromise);
+ };
+
+ scheduleNextTick(() => {
+ called++;
+ });
+
+ orgSetTimeout(() => {
+ try {
+ assert.equal(promiseResolveCalls, 1, "Expected Promise fallback to be used for first callback");
+
+ theGlobal.Promise = null;
+ scheduleNextTick(() => {
+ called++;
+ });
+
+ orgSetTimeout(() => {
+ try {
+ assert.equal(promiseResolveCalls, 1, "Expected no additional Promise.resolve() calls once Promise is unavailable");
+ assert.equal(called, 2, "Expected both callbacks to execute");
+ done();
+ } catch (e) {
+ done(e as Error);
+ }
+ }, 10);
+ } catch (e) {
+ done(e as Error);
+ } finally {
+ orgPromise.resolve = orgPromiseResolve;
+ }
+ }, 10);
+ });
+
+ it("throws RangeError when maxQueuedTasks is exceeded", () => {
+ let theGlobal: any = getGlobal();
+ let thrown: Error | undefined;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+ theGlobal.Promise = {
+ resolve: () => ({
+ then: () => {
+ // Keep the queue pending so depth can be exceeded synchronously.
+ }
+ })
+ };
+
+ scheduleNextTick(() => {
+ // no-op
+ }, {
+ maxQueuedTasks: 2
+ });
+
+ scheduleNextTick(() => {
+ // no-op
+ }, {
+ maxQueuedTasks: 2
+ });
+
+ try {
+ scheduleNextTick(() => {
+ // no-op
+ }, {
+ maxQueuedTasks: 2
+ });
+ } catch (e) {
+ thrown = e as Error;
+ }
+
+ assert.isOk(thrown, "Expected an error when maxQueuedTasks is exceeded");
+ assert.equal(thrown && thrown.name, "RangeError", "Expected a RangeError");
+ assert.equal(thrown && thrown.message, "scheduleNextTick() queue depth exceeded [2]", "Expected Node-like nextTick depth error message");
+ });
+
+ it("uses Node-compatible default maxQueuedTasks depth for nextTick fallback", () => {
+ let theGlobal: any = getGlobal();
+ let thrown: Error | undefined;
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+ theGlobal.Promise = {
+ resolve: () => ({
+ then: () => {
+ // Keep the queue pending so depth can be exceeded synchronously.
+ }
+ })
+ };
+
+ for (let lp = 0; lp < 1000; lp++) {
+ scheduleNextTick(() => {
+ // no-op
+ });
+ }
+
+ try {
+ scheduleNextTick(() => {
+ // no-op
+ });
+ } catch (e) {
+ thrown = e as Error;
+ }
+
+ assert.isOk(thrown, "Expected an error when default nextTick depth is exceeded");
+ assert.equal(thrown && thrown.name, "RangeError", "Expected a RangeError");
+ assert.equal(thrown && thrown.message, "scheduleNextTick() queue depth exceeded [1000]", "Expected Node-like nextTick depth error message");
+ });
+
+ it("recovers Promise fallback when Promise implementation changes while previous batch is stuck", (done) => {
+ let theGlobal: any = getGlobal();
+ let events: string[] = [];
+
+ if (!orgPromise || !orgPromise.resolve) {
+ done();
+ return;
+ }
+
+ theGlobal.process = {
+ version: orgProcess && orgProcess.version,
+ versions: orgProcess && orgProcess.versions,
+ nextTick: null
+ };
+
+ // Simulate a Promise implementation that never resolves callbacks, leaving pending state stuck.
+ theGlobal.Promise = {
+ resolve: () => ({
+ then: () => {
+ // Never invoke callback
+ }
+ })
+ };
+
+ scheduleNextTick(() => {
+ events.push("first");
+ });
+
+ // Switch to a working Promise implementation; scheduling again should recover and flush the queue.
+ theGlobal.Promise = orgPromise;
+ scheduleNextTick(() => {
+ events.push("second");
+ });
+
+ orgSetTimeout(() => {
+ try {
+ assert.deepEqual(events, ["first", "second"], "Expected queued nextTick callbacks to flush after Promise implementation change");
+ done();
+ } catch (e) {
+ done(e as Error);
+ }
+ }, 10);
+ });
+});
diff --git a/lib/test/src/common/timer/timeout.test.ts b/lib/test/src/common/timer/timeout.test.ts
index f9d9a7e6..6765492d 100644
--- a/lib/test/src/common/timer/timeout.test.ts
+++ b/lib/test/src/common/timer/timeout.test.ts
@@ -9,7 +9,6 @@
import * as sinon from "sinon";
import { assert } from "@nevware21/tripwire-chai";
import { createTimeout, createTimeoutWith, scheduleTimeout, scheduleTimeoutWith, setTimeoutOverrides, setGlobalTimeoutOverrides, TimeoutOverrideFuncs } from "../../../../src/timer/timeout";
-import { isNode } from "../../../../src/helpers/environment";
describe("timeout tests", () => {
let clock: sinon.SinonFakeTimers;
@@ -326,6 +325,12 @@ describe("timeout tests", () => {
it("basic timeout", () => {
let timeoutCalled = false;
+ let timerId = setTimeout(() => {
+ // no-op
+ }, 1);
+ let supportsTimerRefresh = !!(timerId && (timerId as any).refresh);
+ clearTimeout(timerId);
+
let theTimeout = scheduleTimeoutWith(newSetTimeoutFn, () => {
timeoutCalled = true;
}, 100);
@@ -363,19 +368,19 @@ describe("timeout tests", () => {
clock.tick(1);
assert.equal(theTimeout.enabled, true, "Check that the handler is stopped");
assert.equal(timeoutCalled, false, "Timeout should have been called yet");
- assert.equal(overrideCalled, isNode() ? 2 : 3, "The override should have been called");
+ assert.equal(overrideCalled, supportsTimerRefresh ? 2 : 3, "The override should have been called");
clock.tick(98);
assert.equal(theTimeout.enabled, true, "Check that the handler is stopped");
assert.equal(timeoutCalled, false, "Timeout should have been called yet");
- assert.equal(overrideCalled, isNode() ? 2 : 3, "The override should have been called");
+ assert.equal(overrideCalled, supportsTimerRefresh ? 2 : 3, "The override should have been called");
clock.tick(1);
assert.equal(theTimeout.enabled, false, "Check that the handler is stopped");
assert.equal(timeoutCalled, true, "Timeout should have been called yet");
- assert.equal(overrideCalled, isNode() ? 2 : 3, "The override should have been called");
+ assert.equal(overrideCalled, supportsTimerRefresh ? 2 : 3, "The override should have been called");
});
});
diff --git a/package.json b/package.json
index 7c878a40..81391a21 100644
--- a/package.json
+++ b/package.json
@@ -107,7 +107,10 @@
"interval",
"microtask",
"queueMicrotask",
+ "nextTick",
+ "process-nexttick",
"cancellable microtask",
+ "function-bind",
"includes",
"string contains",
"html encode",
@@ -147,6 +150,7 @@
"array-reduce",
"array-filter",
"array-find",
+ "array-concat",
"object-copy",
"object-keys",
"lightweight",