already
Advanced tools
Comparing version 3.1.1 to 3.2.0
@@ -79,3 +79,6 @@ declare const _default: { | ||
} | ||
export declare type FilterMapOptions = Partial<ConcurrencyOptions>; | ||
export interface ChunkOptions { | ||
chunk: number | 'idle'; | ||
} | ||
export declare type FilterMapOptions = Partial<ConcurrencyOptions | ChunkOptions>; | ||
export declare type MapArray<T> = Array<T | PromiseLike<T>> | ConcatArray<T | PromiseLike<T>>; | ||
@@ -82,0 +85,0 @@ export declare type MapFn<T, U> = (t: T, index: number, arr: MapArray<T>) => U | Promise<U>; |
@@ -91,3 +91,5 @@ export default { | ||
} | ||
const defaultFilterMapOptions = { concurrency: Infinity }; | ||
const defaultFilterMapOptions = { | ||
concurrency: Infinity | ||
}; | ||
export function filter(arr, opts, filterFn) { | ||
@@ -129,8 +131,60 @@ if (Array.isArray(arr)) { | ||
: arr; | ||
const promiseMapFn = (t, index, arr) => Promise.resolve(mapFn(t, index, arr)); | ||
const { concurrency = Infinity } = opts; | ||
const promiseMapFn = (t, index, arr) => Promise.resolve(mapFn(t, index, arr)); | ||
const { chunk } = opts; | ||
if (typeof chunk !== 'undefined') { | ||
if (typeof chunk !== 'number' && chunk !== 'idle') | ||
throw new Error(`Invalid 'chunk' option to 'map': ${chunk}`); | ||
const useIdle = chunk === 'idle'; | ||
const timeout = chunk === 'idle' | ||
? 15 // 15ms, allow 1.666ms for browser work (to fill up 16.666 ms) | ||
: chunk; | ||
const looper = async (cb) => new Promise((resolve, reject) => { | ||
const resolver = async (timeout) => { | ||
try { | ||
await cb(timeout); | ||
resolve(); | ||
} | ||
catch (err) { | ||
reject(err); | ||
} | ||
}; | ||
(useIdle && typeof requestIdleCallback !== 'undefined') | ||
? requestIdleCallback(idleDeadline => resolver( | ||
// Round down to millisecond, subtract 1. | ||
// That gives a little bit of time for the browser | ||
// to do whatever it wants, and will to some | ||
// degree prevent us to step over the time budget. | ||
Math.floor(idleDeadline.timeRemaining()) - 1), { timeout: 0 }) | ||
: setTimeout(() => resolver(timeout), 0); | ||
}); | ||
return (t) => { | ||
return Promise.resolve(t) | ||
.then(async (values) => { | ||
const arr = toReadonlyArray(values); | ||
const ret = []; | ||
let i = 0; | ||
const loop = async (timeout) => { | ||
const start = Date.now(); | ||
for (; i < arr.length;) { | ||
const u = await promiseMapFn(await arr[i], i, arr); | ||
ret.push(u); | ||
++i; | ||
if (Date.now() - start >= timeout) { | ||
await looper(loop); | ||
break; | ||
} | ||
} | ||
}; | ||
await loop(timeout); | ||
return ret; | ||
}) | ||
.then(values => Promise.all(values)); | ||
}; | ||
} | ||
const concurrently = concurrent(concurrency); | ||
return (t) => { | ||
return Promise.resolve(t) | ||
.then((values) => toReadonlyArray(values).map((val, index, arr) => (() => Promise.resolve(val))() | ||
.then((values) => toReadonlyArray(values) | ||
.map((val, index, arr) => (() => Promise.resolve(val))() | ||
.then((val) => concurrently(promiseMapFn, val, index, arr)))) | ||
@@ -137,0 +191,0 @@ .then(values => Promise.all(values)); |
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "3.1.1", | ||
"version": "3.2.0", | ||
"author": "Gustaf Räntilä <g.rantila@gmail.com>", | ||
@@ -53,7 +53,7 @@ "repository": { | ||
"@babel/plugin-syntax-optional-chaining": "^7.8.3", | ||
"@babel/plugin-transform-modules-commonjs": "^7.15.4", | ||
"@babel/preset-typescript": "^7.15.0", | ||
"@babel/plugin-transform-modules-commonjs": "^7.16.5", | ||
"@babel/preset-typescript": "^7.16.5", | ||
"@types/jest": "^26.0.15", | ||
"commitizen": "^4.2.2", | ||
"concurrently": "^6.2.1", | ||
"concurrently": "^6.5.1", | ||
"cz-conventional-changelog": "^3.3.0", | ||
@@ -66,3 +66,3 @@ "jest": "^26.6.3", | ||
"tslint": "6.1.3", | ||
"typescript": "4.4.3" | ||
"typescript": "4.5.4" | ||
}, | ||
@@ -69,0 +69,0 @@ "dependencies": {}, |
@@ -255,2 +255,25 @@ [![npm version][npm-image]][npm-url] | ||
### filter operations chunked by idle time | ||
Some filter operations (predicate functions) are heavy on calculations. To not starve the system (e.g. a browser) from CPU resources, the filter can be chopped up in smaller chunks with either a `setTimeout(0)` or by using [`requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback). | ||
The options used to specify concurrency can instead specify `chunk`. This implies a concurrency of 1, i.e. no concurrency. Chunking is mostly useful in synchronously heavy operations, not asynchronous. | ||
Specify a chunk time explicitly, e.g. 50ms: | ||
```ts | ||
import { filter } from 'already' | ||
const outArray = await filter( inArray, { chunk: 50 }, filterFun ); | ||
``` | ||
or use `requestIdleCallback` to try to maintain a hang-free experience in browsers: | ||
```ts | ||
import { filter } from 'already' | ||
const outArray = await filter( inArray, { chunk: 'idle' }, filterFun ); | ||
``` | ||
## map | ||
@@ -290,3 +313,23 @@ | ||
### map operations chunked by idle time | ||
Some map operations (predicate functions) are heavy on calculations, just like `filter`. And for [the same reasons](#filter-operations-chunked-by-idle-time), you can select `chunk` to chunk up a map operation to not starve system from CPU resources in (synchronously) heavy map operations: | ||
Specify a chunk time explicitly, e.g. 50ms: | ||
```ts | ||
import { map } from 'already' | ||
const outArray = await map( inArray, { chunk: 50 }, mapFun ); | ||
``` | ||
or use `requestIdleCallback` to try to maintain a hang-free experience in browsers: | ||
```ts | ||
import { map } from 'already' | ||
const outArray = await map( inArray, { chunk: 'idle' }, mapFun ); | ||
``` | ||
## flatMap | ||
@@ -293,0 +336,0 @@ |
71921
871
952