socket-function 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SocketFunction.ts +3 -2
- package/index.d.ts +8 -1
- package/package.json +1 -1
- package/src/CallFactory.ts +192 -31
- package/src/batching.d.ts +8 -1
- package/src/batching.ts +37 -0
package/SocketFunction.ts
CHANGED
|
@@ -17,6 +17,7 @@ import cborx from "cbor-x";
|
|
|
17
17
|
import { setFlag } from "./require/compileFlags";
|
|
18
18
|
import { isNode } from "./src/misc";
|
|
19
19
|
import { getPendingCallCount, harvestCallTimes, harvestFailedCallCount } from "./src/CallFactory";
|
|
20
|
+
import { measureWrap } from "./src/profiling/measure";
|
|
20
21
|
|
|
21
22
|
setFlag(require, "cbor-x", "allowclient", true);
|
|
22
23
|
let cborxInstance = new cborx.Encoder({ structuredClone: true });
|
|
@@ -72,8 +73,8 @@ export class SocketFunction {
|
|
|
72
73
|
// before it is changed, things just break. Also, it needs to be changed on both sides,
|
|
73
74
|
// or else things break. Also, it is very hard to detect when the issue is different serializers
|
|
74
75
|
public static readonly WIRE_SERIALIZER = {
|
|
75
|
-
serialize: (obj: unknown): MaybePromise<Buffer[]> => [cborxInstance.encode(obj)],
|
|
76
|
-
deserialize: (buffers: Buffer[]): MaybePromise<unknown> => cborxInstance.decode(buffers[0]),
|
|
76
|
+
serialize: measureWrap((obj: unknown): MaybePromise<Buffer[]> => [cborxInstance.encode(obj)], "WIRE_SERIALIZER|serialize"),
|
|
77
|
+
deserialize: measureWrap((buffers: Buffer[]): MaybePromise<unknown> => cborxInstance.decode(buffers[0]), "WIRE_SERIALIZER|deserialize"),
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
public static WIRE_WARN_TIME = 100;
|
package/index.d.ts
CHANGED
|
@@ -565,7 +565,7 @@ declare module "socket-function/src/args" {
|
|
|
565
565
|
}
|
|
566
566
|
|
|
567
567
|
declare module "socket-function/src/batching" {
|
|
568
|
-
import { AnyFunction } from "socket-function/src/types";
|
|
568
|
+
import { AnyFunction, MaybePromise } from "socket-function/src/types";
|
|
569
569
|
export type DelayType = (number | "afterio" | "immediate" | "afterpromises" | "paintLoop" | "afterPaint");
|
|
570
570
|
export declare function delay(delayTime: DelayType, immediateShortDelays?: "immediateShortDelays"): Promise<void>;
|
|
571
571
|
export declare function batchFunctionNone<Arg, Result = void>(config: unknown, fnc: (arg: Arg[]) => (Promise<Result> | Result)): (arg: Arg) => Promise<Result>;
|
|
@@ -596,6 +596,13 @@ declare module "socket-function/src/batching" {
|
|
|
596
596
|
minDelay?: number;
|
|
597
597
|
maxDelay?: number;
|
|
598
598
|
}): T;
|
|
599
|
+
export declare const safeLoop: typeof unblockLoop;
|
|
600
|
+
export declare const throttledLoop: typeof unblockLoop;
|
|
601
|
+
export declare function unblockLoop<T, R>(config: {
|
|
602
|
+
data: T[];
|
|
603
|
+
maxBlockingTime?: number;
|
|
604
|
+
backOffTime?: number;
|
|
605
|
+
} | T[], fnc: (item: T) => MaybePromise<R>): Promise<R[]>;
|
|
599
606
|
|
|
600
607
|
}
|
|
601
608
|
|
package/package.json
CHANGED
package/src/CallFactory.ts
CHANGED
|
@@ -269,7 +269,7 @@ export async function createCallFactory(
|
|
|
269
269
|
let arg = originalArgs[0] as any;
|
|
270
270
|
fncHack = `.${arg.DomainName}.${arg.ModuleId}.${arg.FunctionId}`;
|
|
271
271
|
}
|
|
272
|
-
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) +
|
|
272
|
+
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + `B`).padEnd(4, " ")}\t${formatNumber(data.length)} buffers\tREMOTE CALL\t${call.classGuid}.${call.functionName}${fncHack} at ${Date.now()}`);
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
// If sending OR resultPromise throws, we want to error out. This solves some issues with resultPromise
|
|
@@ -449,6 +449,7 @@ export async function createCallFactory(
|
|
|
449
449
|
|
|
450
450
|
let pendingCall: MessageHeader & {
|
|
451
451
|
buffers: Buffer[];
|
|
452
|
+
firstReceivedTime?: number;
|
|
452
453
|
} | undefined;
|
|
453
454
|
|
|
454
455
|
async function processPendingCall() {
|
|
@@ -523,7 +524,6 @@ export async function createCallFactory(
|
|
|
523
524
|
if (call.isReturn) {
|
|
524
525
|
if (!SocketFunction.LEGACY_INITIALIZE && call.seqNum === INITIALIZE_STATE_SEQ_NUM) {
|
|
525
526
|
callFactory.receivedInitializeState = call.result as InitializeState;
|
|
526
|
-
console.log(green(`Received initialize state: ${JSON.stringify(callFactory.receivedInitializeState)}`));
|
|
527
527
|
return;
|
|
528
528
|
}
|
|
529
529
|
let callbackObj = pendingCalls.get(call.seqNum);
|
|
@@ -536,10 +536,10 @@ export async function createCallFactory(
|
|
|
536
536
|
}
|
|
537
537
|
if (SocketFunction.logMessages) {
|
|
538
538
|
let call = callbackObj.call;
|
|
539
|
-
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\tRETURN\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
|
|
539
|
+
console.log(`SIZE\t${(formatNumberSuffixed(resultSize) + "B").padEnd(4, " ")}\t${formatNumber(currentBuffers.length)} buffers\tRETURN\t${call.classGuid}.${call.functionName} at ${Date.now()}, (${nodeId} / ${localNodeId})`);
|
|
540
540
|
}
|
|
541
541
|
if (call.isResultCompressed === "LZ4") {
|
|
542
|
-
call.result = await decompressObjLZ4(call.result as Buffer, receiveStats);
|
|
542
|
+
call.result = await decompressObjLZ4(call.result as Buffer[], receiveStats);
|
|
543
543
|
call.isResultCompressed = undefined;
|
|
544
544
|
} else if (call.isResultCompressed === "zip" || call.isResultCompressed === true) {
|
|
545
545
|
call.result = await decompressObj(call.result as Buffer, receiveStats);
|
|
@@ -548,7 +548,7 @@ export async function createCallFactory(
|
|
|
548
548
|
callbackObj.callback(call);
|
|
549
549
|
} else {
|
|
550
550
|
if (call.isArgsCompressed === "LZ4") {
|
|
551
|
-
call.args = await decompressObjLZ4(call.args as any as Buffer, sendStats) as any;
|
|
551
|
+
call.args = await decompressObjLZ4(call.args as any as Buffer[], sendStats) as any;
|
|
552
552
|
call.isArgsCompressed = undefined;
|
|
553
553
|
} else if (call.isArgsCompressed === "zip" || call.isArgsCompressed === true) {
|
|
554
554
|
call.args = await decompressObj(call.args as any as Buffer, sendStats) as any;
|
|
@@ -576,12 +576,12 @@ export async function createCallFactory(
|
|
|
576
576
|
if (callFactory.receivedInitializeState?.supportsLZ4) {
|
|
577
577
|
let compressMode = shouldCompressCall(call);
|
|
578
578
|
if (compressMode !== false) {
|
|
579
|
-
response.result = await compressObjLZ4(response.result, sendStats)
|
|
579
|
+
response.result = await compressObjLZ4(response.result, sendStats);
|
|
580
580
|
response.isResultCompressed = "LZ4";
|
|
581
581
|
}
|
|
582
582
|
} else {
|
|
583
583
|
if (shouldCompressCall(call)) {
|
|
584
|
-
response.result = await compressObj(response.result, sendStats)
|
|
584
|
+
response.result = await compressObj(response.result, sendStats);
|
|
585
585
|
response.isResultCompressed = "zip";
|
|
586
586
|
}
|
|
587
587
|
}
|
|
@@ -693,11 +693,19 @@ export async function createCallFactory(
|
|
|
693
693
|
return;
|
|
694
694
|
}
|
|
695
695
|
if (message instanceof Buffer) {
|
|
696
|
-
if (message.byteLength > 1000 * 1000 * 10) {
|
|
697
|
-
console.log(`Received large packet ${formatNumber(message.byteLength)}B at ${Date.now()}`);
|
|
698
|
-
}
|
|
699
696
|
if (!pendingCall) {
|
|
700
|
-
throw new Error(`Received data without size`);
|
|
697
|
+
throw new Error(`Received data without size ${message.byteLength}B, first 100 bytes: ${message.slice(0, 100).toString("hex")}`);
|
|
698
|
+
}
|
|
699
|
+
if (message.byteLength > 1000 * 1000 * 10 || pendingCall.bufferCount > 1000 && (pendingCall.buffers.length % 100 === 0)) {
|
|
700
|
+
if (pendingCall.buffers.length === 0) {
|
|
701
|
+
console.log(`Received large/many packets ${formatNumber(message.byteLength)}B (${pendingCall.buffers.length} / ${pendingCall.bufferCount}) at ${Date.now()}`);
|
|
702
|
+
} else {
|
|
703
|
+
let elapsed = Date.now() - (pendingCall.firstReceivedTime || 0);
|
|
704
|
+
console.log(`Received large/many packets ${formatNumber(message.byteLength)}B (${pendingCall.buffers.length} / ${pendingCall.bufferCount}) after ${formatTime(elapsed)}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (pendingCall.buffers.length === 0) {
|
|
708
|
+
pendingCall.firstReceivedTime = Date.now();
|
|
701
709
|
}
|
|
702
710
|
pendingCall.buffers.push(message);
|
|
703
711
|
if (pendingCall.buffers.length !== pendingCall.bufferCount) {
|
|
@@ -771,6 +779,9 @@ type CompressionStats = {
|
|
|
771
779
|
|
|
772
780
|
const compressObj = measureWrap(async function wireCallCompress(obj: unknown, stats: CompressionStats): Promise<Buffer> {
|
|
773
781
|
let buffers = await SocketFunction.WIRE_SERIALIZER.serialize(obj);
|
|
782
|
+
if (buffers.length > 1) {
|
|
783
|
+
throw new Error("Legacy CompressObj only supports single buffer");
|
|
784
|
+
}
|
|
774
785
|
let lengthBuffer = Buffer.from((new Float64Array(buffers.map(x => x.length))).buffer);
|
|
775
786
|
let buffer = Buffer.concat([lengthBuffer, ...buffers]);
|
|
776
787
|
stats.uncompressedSize += buffer.length;
|
|
@@ -794,27 +805,177 @@ const decompressObj = measureWrap(async function wireCallDecompress(obj: Buffer,
|
|
|
794
805
|
return result;
|
|
795
806
|
});
|
|
796
807
|
|
|
797
|
-
const compressObjLZ4 = measureWrap(async function wireCallCompressLZ4(obj: unknown, stats: CompressionStats): Promise<Buffer> {
|
|
798
|
-
let
|
|
799
|
-
let
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
808
|
+
const compressObjLZ4 = measureWrap(async function wireCallCompressLZ4(obj: unknown, stats: CompressionStats): Promise<Buffer[]> {
|
|
809
|
+
let headerParts: number[];
|
|
810
|
+
let dataBuffers: Buffer[];
|
|
811
|
+
|
|
812
|
+
if (obj instanceof Buffer) {
|
|
813
|
+
headerParts = [1];
|
|
814
|
+
dataBuffers = [obj];
|
|
815
|
+
} else if (Array.isArray(obj) && obj.every((x: any) => x instanceof Buffer)) {
|
|
816
|
+
let bufferArray = obj as Buffer[];
|
|
817
|
+
const TARGET_SIZE = 50 * 1024 * 1024;
|
|
818
|
+
const MIN_INDIVIDUAL_SIZE = 10 * 1024 * 1024;
|
|
819
|
+
const MAX_UNSPLIT_SIZE = 100 * 1024 * 1024;
|
|
820
|
+
|
|
821
|
+
let outputBuffers: Buffer[] = [];
|
|
822
|
+
let outputDescriptors: number[][] = [];
|
|
823
|
+
let currentGroup: Buffer[] = [];
|
|
824
|
+
let currentGroupSize = 0;
|
|
825
|
+
|
|
826
|
+
function flushCurrentGroup() {
|
|
827
|
+
if (currentGroup.length > 0) {
|
|
828
|
+
outputBuffers.push(Buffer.concat(currentGroup));
|
|
829
|
+
outputDescriptors.push(currentGroup.map(b => b.length));
|
|
830
|
+
currentGroup = [];
|
|
831
|
+
currentGroupSize = 0;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
for (let buf of bufferArray) {
|
|
836
|
+
if (buf.length >= MIN_INDIVIDUAL_SIZE) {
|
|
837
|
+
flushCurrentGroup();
|
|
838
|
+
|
|
839
|
+
if (buf.length > MAX_UNSPLIT_SIZE) {
|
|
840
|
+
let offset = 0;
|
|
841
|
+
while (offset < buf.length) {
|
|
842
|
+
let chunkSize = Math.min(TARGET_SIZE, buf.length - offset);
|
|
843
|
+
outputBuffers.push(buf.slice(offset, offset + chunkSize));
|
|
844
|
+
outputDescriptors.push([chunkSize]);
|
|
845
|
+
offset += chunkSize;
|
|
846
|
+
}
|
|
847
|
+
} else {
|
|
848
|
+
outputBuffers.push(buf);
|
|
849
|
+
outputDescriptors.push([buf.length]);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
currentGroup.push(buf);
|
|
853
|
+
currentGroupSize += buf.length;
|
|
854
|
+
|
|
855
|
+
if (currentGroupSize >= TARGET_SIZE) {
|
|
856
|
+
flushCurrentGroup();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
flushCurrentGroup();
|
|
862
|
+
|
|
863
|
+
headerParts = [2, outputBuffers.length];
|
|
864
|
+
for (let descriptor of outputDescriptors) {
|
|
865
|
+
headerParts.push(descriptor.length, ...descriptor);
|
|
866
|
+
}
|
|
867
|
+
dataBuffers = outputBuffers;
|
|
868
|
+
} else {
|
|
869
|
+
let buffers = await SocketFunction.WIRE_SERIALIZER.serialize(obj);
|
|
870
|
+
headerParts = [3, buffers.length];
|
|
871
|
+
dataBuffers = buffers;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
let headerBuffer = Buffer.from((new Float64Array(headerParts)).buffer);
|
|
875
|
+
let allBuffers = [headerBuffer, ...dataBuffers];
|
|
876
|
+
|
|
877
|
+
stats.uncompressedSize += allBuffers.reduce((sum, buf) => sum + buf.length, 0);
|
|
878
|
+
|
|
879
|
+
let compressed: Buffer[] = [];
|
|
880
|
+
let startTime = Date.now();
|
|
881
|
+
let lastWarnTime = startTime;
|
|
882
|
+
let currentUncompressedSize = 0;
|
|
883
|
+
let currentCompressedSize = 0;
|
|
884
|
+
|
|
885
|
+
function logIfSlow(i: number) {
|
|
886
|
+
let now = Date.now();
|
|
887
|
+
if (now - lastWarnTime > 500) {
|
|
888
|
+
let elapsed = now - startTime;
|
|
889
|
+
console.log(`Slow LZ4 compress (${formatTime(elapsed)}: ${i + 1}/${allBuffers.length} buffers, ${formatNumber(currentUncompressedSize)}B => ${formatNumber(currentCompressedSize)}B`);
|
|
890
|
+
lastWarnTime = now;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
for (let i = 0; i < allBuffers.length; i++) {
|
|
895
|
+
let buf = allBuffers[i];
|
|
896
|
+
currentUncompressedSize += buf.length;
|
|
897
|
+
let compressedBuf = LZ4.compress(buf);
|
|
898
|
+
compressed.push(compressedBuf);
|
|
899
|
+
currentCompressedSize += compressedBuf.length;
|
|
900
|
+
|
|
901
|
+
logIfSlow(i);
|
|
902
|
+
}
|
|
903
|
+
logIfSlow(allBuffers.length);
|
|
904
|
+
|
|
905
|
+
stats.compressedSize += currentCompressedSize;
|
|
906
|
+
|
|
907
|
+
return compressed;
|
|
805
908
|
});
|
|
806
909
|
|
|
807
|
-
const decompressObjLZ4 = measureWrap(async function wireCallDecompressLZ4(obj: Buffer, stats: CompressionStats): Promise<unknown> {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
let
|
|
811
|
-
let
|
|
812
|
-
let
|
|
813
|
-
let
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
910
|
+
const decompressObjLZ4 = measureWrap(async function wireCallDecompressLZ4(obj: Buffer[], stats: CompressionStats): Promise<unknown> {
|
|
911
|
+
stats.compressedSize += obj.reduce((sum, buf) => sum + buf.length, 0);
|
|
912
|
+
|
|
913
|
+
let decompressed: Buffer[] = [];
|
|
914
|
+
let startTime = Date.now();
|
|
915
|
+
let lastWarnTime = startTime;
|
|
916
|
+
let currentCompressedSize = 0;
|
|
917
|
+
let currentUncompressedSize = 0;
|
|
918
|
+
function logIfSlow(i: number) {
|
|
919
|
+
let now = Date.now();
|
|
920
|
+
if (now - lastWarnTime > 500) {
|
|
921
|
+
let elapsed = now - startTime;
|
|
922
|
+
console.log(`Slow LZ4 decompress (${formatTime(elapsed)}): ${i + 1}/${obj.length} buffers, ${formatNumber(currentCompressedSize)}B => ${formatNumber(currentUncompressedSize)}B`);
|
|
923
|
+
lastWarnTime = now;
|
|
924
|
+
}
|
|
817
925
|
}
|
|
818
|
-
|
|
819
|
-
|
|
926
|
+
|
|
927
|
+
for (let i = 0; i < obj.length; i++) {
|
|
928
|
+
let buf = obj[i];
|
|
929
|
+
currentCompressedSize += buf.length;
|
|
930
|
+
let decompressedBuf = LZ4.decompress(buf);
|
|
931
|
+
decompressed.push(decompressedBuf);
|
|
932
|
+
currentUncompressedSize += decompressedBuf.length;
|
|
933
|
+
|
|
934
|
+
logIfSlow(i);
|
|
935
|
+
}
|
|
936
|
+
logIfSlow(obj.length);
|
|
937
|
+
|
|
938
|
+
stats.uncompressedSize += currentUncompressedSize;
|
|
939
|
+
|
|
940
|
+
let headerBuffer = decompressed[0];
|
|
941
|
+
let dataBuffers = decompressed.slice(1);
|
|
942
|
+
|
|
943
|
+
let typeBuffer = headerBuffer.slice(0, 8);
|
|
944
|
+
let type = new Float64Array(typeBuffer.buffer, typeBuffer.byteOffset, 1)[0];
|
|
945
|
+
|
|
946
|
+
if (type === 1) {
|
|
947
|
+
return dataBuffers[0];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (type === 2) {
|
|
951
|
+
let headerData = new Float64Array(headerBuffer.buffer, headerBuffer.byteOffset, headerBuffer.byteLength / 8);
|
|
952
|
+
let outputBufferCount = headerData[1];
|
|
953
|
+
|
|
954
|
+
let buffers: Buffer[] = [];
|
|
955
|
+
let headerIndex = 2;
|
|
956
|
+
|
|
957
|
+
for (let i = 0; i < outputBufferCount; i++) {
|
|
958
|
+
let inputBufferCount = headerData[headerIndex++];
|
|
959
|
+
let sizes: number[] = [];
|
|
960
|
+
for (let j = 0; j < inputBufferCount; j++) {
|
|
961
|
+
sizes.push(headerData[headerIndex++]);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
let outputBuffer = dataBuffers[i];
|
|
965
|
+
let offset = 0;
|
|
966
|
+
for (let size of sizes) {
|
|
967
|
+
buffers.push(outputBuffer.slice(offset, offset + size));
|
|
968
|
+
offset += size;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return buffers;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (type === 3) {
|
|
976
|
+
let result = await SocketFunction.WIRE_SERIALIZER.deserialize(dataBuffers);
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
throw new Error(`Unknown compression type ${type}`);
|
|
820
981
|
});
|
package/src/batching.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyFunction } from "./types";
|
|
1
|
+
import { AnyFunction, MaybePromise } from "./types";
|
|
2
2
|
export type DelayType = (number | "afterio" | "immediate" | "afterpromises" | "paintLoop" | "afterPaint");
|
|
3
3
|
export declare function delay(delayTime: DelayType, immediateShortDelays?: "immediateShortDelays"): Promise<void>;
|
|
4
4
|
export declare function batchFunctionNone<Arg, Result = void>(config: unknown, fnc: (arg: Arg[]) => (Promise<Result> | Result)): (arg: Arg) => Promise<Result>;
|
|
@@ -29,3 +29,10 @@ export declare function retryFunctional<T extends AnyFunction>(fnc: T, config?:
|
|
|
29
29
|
minDelay?: number;
|
|
30
30
|
maxDelay?: number;
|
|
31
31
|
}): T;
|
|
32
|
+
export declare const safeLoop: typeof unblockLoop;
|
|
33
|
+
export declare const throttledLoop: typeof unblockLoop;
|
|
34
|
+
export declare function unblockLoop<T, R>(config: {
|
|
35
|
+
data: T[];
|
|
36
|
+
maxBlockingTime?: number;
|
|
37
|
+
backOffTime?: number;
|
|
38
|
+
} | T[], fnc: (item: T) => MaybePromise<R>): Promise<R[]>;
|
package/src/batching.ts
CHANGED
|
@@ -329,4 +329,41 @@ export function retryFunctional<T extends AnyFunction>(fnc: T, config?: {
|
|
|
329
329
|
return async function (...args: any[]) {
|
|
330
330
|
return await runFnc(args, maxRetries);
|
|
331
331
|
} as any;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const safeLoop = unblockLoop;
|
|
335
|
+
export const throttledLoop = unblockLoop;
|
|
336
|
+
export async function unblockLoop<T, R>(config: {
|
|
337
|
+
data: T[];
|
|
338
|
+
maxBlockingTime?: number;
|
|
339
|
+
backOffTime?: number;
|
|
340
|
+
} | T[], fnc: (item: T) => MaybePromise<R>): Promise<R[]> {
|
|
341
|
+
|
|
342
|
+
let data = Array.isArray(config) ? config : config.data;
|
|
343
|
+
let maxBlockingTime = 300;
|
|
344
|
+
let backOffTime = 50;
|
|
345
|
+
if (!Array.isArray(config)) {
|
|
346
|
+
maxBlockingTime = config.maxBlockingTime ?? 300;
|
|
347
|
+
backOffTime = config.backOffTime ?? 50;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let lastYieldTime = Date.now();
|
|
351
|
+
let results: R[] = [];
|
|
352
|
+
|
|
353
|
+
for (let item of data) {
|
|
354
|
+
let result = fnc(item);
|
|
355
|
+
|
|
356
|
+
// If the function returns a promise, await it
|
|
357
|
+
if (result && typeof result === "object" && "then" in result) {
|
|
358
|
+
result = await result;
|
|
359
|
+
}
|
|
360
|
+
results.push(result);
|
|
361
|
+
|
|
362
|
+
// Check if we've been blocking for too long
|
|
363
|
+
if (Date.now() - lastYieldTime > maxBlockingTime) {
|
|
364
|
+
await delay(backOffTime);
|
|
365
|
+
lastYieldTime = Date.now();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return results;
|
|
332
369
|
}
|