@tim-code/my-util 0.4.0 → 0.4.2
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/package.json +1 -1
- package/src/find.js +70 -0
- package/src/find.test.js +118 -0
- package/src/promise.js +9 -1
- package/src/promise.test.js +25 -1
package/package.json
CHANGED
package/src/find.js
CHANGED
|
@@ -153,6 +153,7 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
|
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
155
|
* Find the closest element in an array. If there is a tie, then returns the first matching element by order in the array.
|
|
156
|
+
* If some values are undefined or null, they will be ignored. If no element is found, returns undefined.
|
|
156
157
|
* If using for strings, need to specify different values for "cutoff" and "comparator".
|
|
157
158
|
* "~" and "" are good cutoff string values for gt/gte and lt/lte respectively.
|
|
158
159
|
* @template T, V
|
|
@@ -267,3 +268,72 @@ export function findMax(array, { key, cutoff = -Infinity } = {}) {
|
|
|
267
268
|
}
|
|
268
269
|
return closest
|
|
269
270
|
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Like Array.prototype.findIndex but starts from given index.
|
|
274
|
+
* @template T
|
|
275
|
+
* @param {Array<T>} array
|
|
276
|
+
* @param {number} fromIndex
|
|
277
|
+
* @param {(value: T, index: number, array: Array<T>) => boolean} callback
|
|
278
|
+
* @returns {number}
|
|
279
|
+
*/
|
|
280
|
+
export function findIndexFrom(array, fromIndex, callback) {
|
|
281
|
+
for (let i = fromIndex; i < array.length; i++) {
|
|
282
|
+
if (callback(array[i], i, array)) {
|
|
283
|
+
return i
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return -1
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Like Array.prototype.find but starts from given index.
|
|
291
|
+
* @template T
|
|
292
|
+
* @param {Array<T>} array
|
|
293
|
+
* @param {number} fromIndex
|
|
294
|
+
* @param {(value: T, index: number, array: Array<T>) => boolean} callback
|
|
295
|
+
* @returns {T|undefined}
|
|
296
|
+
*/
|
|
297
|
+
export function findFrom(array, fromIndex, callback) {
|
|
298
|
+
const foundIndex = findIndexFrom(array, fromIndex, callback)
|
|
299
|
+
if (foundIndex < 0) {
|
|
300
|
+
return undefined
|
|
301
|
+
}
|
|
302
|
+
return array[foundIndex]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Like Array.prototype.findLastIndex but starts from given index.
|
|
307
|
+
* @template T
|
|
308
|
+
* @param {Array<T>} array
|
|
309
|
+
* @param {number} fromIndex If greater or equal to length, coerces to last index in array.
|
|
310
|
+
* @param {(value: T, index: number, array: Array<T>) => boolean} callback
|
|
311
|
+
* @returns {number}
|
|
312
|
+
*/
|
|
313
|
+
export function findLastIndexFrom(array, fromIndex, callback) {
|
|
314
|
+
if (fromIndex >= array.length) {
|
|
315
|
+
fromIndex = array.length - 1
|
|
316
|
+
}
|
|
317
|
+
for (let i = fromIndex; i >= 0; i--) {
|
|
318
|
+
if (callback(array[i], i, array)) {
|
|
319
|
+
return i
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return -1
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Like Array.prototype.findLast but starts from given index.
|
|
327
|
+
* @template T
|
|
328
|
+
* @param {Array<T>} array
|
|
329
|
+
* @param {number} fromIndex
|
|
330
|
+
* @param {(value: T, index: number, array: Array<T>) => boolean} callback
|
|
331
|
+
* @returns {T|undefined}
|
|
332
|
+
*/
|
|
333
|
+
export function findLastFrom(array, fromIndex, callback) {
|
|
334
|
+
const foundIndex = findLastIndexFrom(array, fromIndex, callback)
|
|
335
|
+
if (foundIndex < 0) {
|
|
336
|
+
return undefined
|
|
337
|
+
}
|
|
338
|
+
return array[foundIndex]
|
|
339
|
+
}
|
package/src/find.test.js
CHANGED
|
@@ -9,6 +9,10 @@ const {
|
|
|
9
9
|
findClosest,
|
|
10
10
|
findMin,
|
|
11
11
|
findMax,
|
|
12
|
+
findIndexFrom,
|
|
13
|
+
findFrom,
|
|
14
|
+
findLastIndexFrom,
|
|
15
|
+
findLastFrom,
|
|
12
16
|
} = await import("./find.js")
|
|
13
17
|
|
|
14
18
|
describe("findClosestAbs", () => {
|
|
@@ -284,3 +288,117 @@ describe("findMax", () => {
|
|
|
284
288
|
expect(findMax([3, 1, 4, 2], { cutoff: 4 })).toBeUndefined()
|
|
285
289
|
})
|
|
286
290
|
})
|
|
291
|
+
|
|
292
|
+
describe("findIndexFrom", () => {
|
|
293
|
+
it("returns the index of the first matching element from fromIndex", () => {
|
|
294
|
+
expect(findIndexFrom([1, 2, 3, 4], 1, (x) => x > 2)).toBe(2)
|
|
295
|
+
expect(findIndexFrom([1, 2, 3, 4], 2, (x) => x > 2)).toBe(2)
|
|
296
|
+
expect(findIndexFrom([1, 2, 3, 4], 3, (x) => x > 2)).toBe(3)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it("returns -1 if no element matches", () => {
|
|
300
|
+
expect(findIndexFrom([1, 2, 3, 4], 2, (x) => x > 10)).toBe(-1)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it("returns -1 for empty array", () => {
|
|
304
|
+
expect(findIndexFrom([], 0, () => true)).toBe(-1)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it("passes correct arguments to callback", () => {
|
|
308
|
+
const arr = [10, 20, 30]
|
|
309
|
+
const called = []
|
|
310
|
+
findIndexFrom(arr, 1, (value, index, array) => {
|
|
311
|
+
called.push([value, index, array])
|
|
312
|
+
return false
|
|
313
|
+
})
|
|
314
|
+
expect(called[0][0]).toBe(20)
|
|
315
|
+
expect(called[0][1]).toBe(1)
|
|
316
|
+
expect(called[0][2]).toBe(arr)
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
describe("findFrom", () => {
|
|
321
|
+
it("returns the first matching element from fromIndex", () => {
|
|
322
|
+
expect(findFrom([1, 2, 3, 4], 1, (x) => x > 2)).toBe(3)
|
|
323
|
+
expect(findFrom([1, 2, 3, 4], 2, (x) => x > 2)).toBe(3)
|
|
324
|
+
expect(findFrom([1, 2, 3, 4], 3, (x) => x > 2)).toBe(4)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it("returns undefined if no element matches", () => {
|
|
328
|
+
expect(findFrom([1, 2, 3, 4], 2, (x) => x > 10)).toBeUndefined()
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it("returns undefined for empty array", () => {
|
|
332
|
+
expect(findFrom([], 0, () => true)).toBeUndefined()
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it("passes correct arguments to callback", () => {
|
|
336
|
+
const arr = [10, 20, 30]
|
|
337
|
+
const called = []
|
|
338
|
+
findFrom(arr, 1, (value, index, array) => {
|
|
339
|
+
called.push([value, index, array])
|
|
340
|
+
return false
|
|
341
|
+
})
|
|
342
|
+
expect(called[0][0]).toBe(20)
|
|
343
|
+
expect(called[0][1]).toBe(1)
|
|
344
|
+
expect(called[0][2]).toBe(arr)
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
describe("findLastIndexFrom", () => {
|
|
349
|
+
it("returns the index of the last matching element from fromIndex (backwards)", () => {
|
|
350
|
+
expect(findLastIndexFrom([1, 2, 3, 4], 2, (x) => x < 3)).toBe(1)
|
|
351
|
+
expect(findLastIndexFrom([1, 2, 3, 4], 3, (x) => x < 3)).toBe(1)
|
|
352
|
+
expect(findLastIndexFrom([1, 2, 3, 4], 1, (x) => x < 3)).toBe(1)
|
|
353
|
+
expect(findLastIndexFrom([1, 2, 3, 4], 0, (x) => x < 3)).toBe(0)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it("returns -1 if no element matches", () => {
|
|
357
|
+
expect(findLastIndexFrom([1, 2, 3, 4], 3, (x) => x > 10)).toBe(-1)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it("returns -1 for empty array", () => {
|
|
361
|
+
expect(findLastIndexFrom([], 0, () => true)).toBe(-1)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it("passes correct arguments to callback", () => {
|
|
365
|
+
const arr = [10, 20, 30]
|
|
366
|
+
const called = []
|
|
367
|
+
findLastIndexFrom(arr, 2, (value, index, array) => {
|
|
368
|
+
called.push([value, index, array])
|
|
369
|
+
return false
|
|
370
|
+
})
|
|
371
|
+
expect(called[0][0]).toBe(30)
|
|
372
|
+
expect(called[0][1]).toBe(2)
|
|
373
|
+
expect(called[0][2]).toBe(arr)
|
|
374
|
+
})
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
describe("findLastFrom", () => {
|
|
378
|
+
it("returns the last matching element from fromIndex (backwards)", () => {
|
|
379
|
+
expect(findLastFrom([1, 2, 3, 4], 2, (x) => x < 3)).toBe(2)
|
|
380
|
+
expect(findLastFrom([1, 2, 3, 4], 3, (x) => x < 3)).toBe(2)
|
|
381
|
+
expect(findLastFrom([1, 2, 3, 4], 1, (x) => x < 3)).toBe(2)
|
|
382
|
+
expect(findLastFrom([1, 2, 3, 4], 0, (x) => x < 3)).toBe(1)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it("returns undefined if no element matches", () => {
|
|
386
|
+
expect(findLastFrom([1, 2, 3, 4], 3, (x) => x > 10)).toBeUndefined()
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it("returns undefined for empty array", () => {
|
|
390
|
+
expect(findLastFrom([], 0, () => true)).toBeUndefined()
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it("passes correct arguments to callback", () => {
|
|
394
|
+
const arr = [10, 20, 30]
|
|
395
|
+
const called = []
|
|
396
|
+
findLastFrom(arr, 2, (value, index, array) => {
|
|
397
|
+
called.push([value, index, array])
|
|
398
|
+
return false
|
|
399
|
+
})
|
|
400
|
+
expect(called[0][0]).toBe(30)
|
|
401
|
+
expect(called[0][1]).toBe(2)
|
|
402
|
+
expect(called[0][2]).toBe(arr)
|
|
403
|
+
})
|
|
404
|
+
})
|
package/src/promise.js
CHANGED
|
@@ -70,10 +70,15 @@ export async function sleep(ms) {
|
|
|
70
70
|
* @param {Function=} $1.limiter A function awaited after a group of parallel calls is processed.
|
|
71
71
|
* It is called with the number of parallel calls processed. Could be as simple as `() => sleep(10000)` if you wanted to wait 10 seconds between.
|
|
72
72
|
* @param {boolean=} $1.flatten Flattens values before returning; useful if promises return arrays
|
|
73
|
+
* @param {boolean=} $1.abort If true, will return early if there are errors.
|
|
74
|
+
* If false (default), will process all elements in the array (like Promise.allSettled()).
|
|
73
75
|
* @param {Function} callback
|
|
74
76
|
* @returns {Object} {results, values, returned, errors}
|
|
75
77
|
*/
|
|
76
|
-
export async function allSettled(
|
|
78
|
+
export async function allSettled(
|
|
79
|
+
{ array, limit, limiter, flatten = false, abort = false },
|
|
80
|
+
callback
|
|
81
|
+
) {
|
|
77
82
|
const results = []
|
|
78
83
|
let returned = []
|
|
79
84
|
let values = []
|
|
@@ -92,6 +97,9 @@ export async function allSettled({ array, limit, limiter, flatten = false }, cal
|
|
|
92
97
|
errors.push(reason)
|
|
93
98
|
}
|
|
94
99
|
}
|
|
100
|
+
if (abort && errors.length) {
|
|
101
|
+
break
|
|
102
|
+
}
|
|
95
103
|
await limiter?.(elements.length)
|
|
96
104
|
}
|
|
97
105
|
if (flatten) {
|
package/src/promise.test.js
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
/* eslint-disable prefer-promise-reject-errors */
|
|
3
3
|
import { jest } from "@jest/globals"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
alert,
|
|
7
|
+
allSettled,
|
|
8
|
+
intervalLimiter,
|
|
9
|
+
poll,
|
|
10
|
+
PollError,
|
|
11
|
+
sleep,
|
|
12
|
+
throwFirstReject,
|
|
13
|
+
} from "./promise.js"
|
|
6
14
|
|
|
7
15
|
describe("poll", () => {
|
|
8
16
|
it("resolves immediately if callback returns a non-undefined/null/false value", async () => {
|
|
@@ -182,6 +190,22 @@ describe("allSettled", () => {
|
|
|
182
190
|
expect(limiter).toHaveBeenCalledTimes(3)
|
|
183
191
|
expect(limiterCalls).toEqual([2, 2, 1])
|
|
184
192
|
})
|
|
193
|
+
|
|
194
|
+
it("returns early if abort=true and any error occurs", async () => {
|
|
195
|
+
// Should process only up to the first chunk with a rejection, then stop
|
|
196
|
+
const arr = [1, 2, 3, 4, 5, 6]
|
|
197
|
+
const cb = jest
|
|
198
|
+
.fn()
|
|
199
|
+
.mockImplementation((x) => (x === 2 || x === 4 ? Promise.reject(`fail${x}`) : x))
|
|
200
|
+
// limit=2 so chunks: [1,2], [3,4], [5,6]
|
|
201
|
+
const result = await allSettled({ array: arr, limit: 2, abort: true }, cb)
|
|
202
|
+
// The first chunk: [1,2] => 1 fulfilled, 1 rejected
|
|
203
|
+
// Should stop after first chunk with error
|
|
204
|
+
expect(result.values.length).toBe(2)
|
|
205
|
+
expect(result.errors).toEqual(["fail2"])
|
|
206
|
+
expect(cb).toHaveBeenCalledTimes(2)
|
|
207
|
+
// Should not process [3,4] or [5,6]
|
|
208
|
+
})
|
|
185
209
|
})
|
|
186
210
|
|
|
187
211
|
describe("intervalLimiter", () => {
|