promises-arrow
Version:
`promises-arrow` is a library of (1) higher order functions, such as `map()` and `filter()`where the function returns a Promisel (2) Functions that wait, returning a Promise; (3) Functions that manage retrying an operation N times until it succeeds; (4,5)
136 lines (122 loc) • 5.05 kB
text/typescript
import {DeferredPromise} from "./DeferredPromise";
export class HigherOrderPromise {
static while<T>(fnContinue: () => boolean,
fn: () => Promise<unknown>): Promise<unknown> {
const promise = new DeferredPromise<any>();
whileLocal(fnContinue, fn, promise);
return promise.promise;
}
static for<T>(start: number, pastEnd: number, increment: number,
fn: (t: number) => Promise<any>): Promise<any> {
let value = start;
return HigherOrderPromise.while(
() => value < pastEnd,
() => fn(value).then(() => value += increment)
);
}
// Calls the function with each of the items in turn, one at a time.
// It does not alter the items array
static forEach<T>(items: Array<T>,
fn: (item: T, index: number) => Promise<any>): Promise<any> {
return HigherOrderPromise.for(0, items.length, 1, i => fn(items[i], i));
}
// Calls the function with each of the iterator items in turn, one at a time, recursively.
static forEachIterator<T>(it: Iterator<T>,
fn: (item: T, index: number) => Promise<any>): Promise<any> {
let index = 0
let head: IteratorResult<T>;
return HigherOrderPromise.while(
() => {
head = it.next();
return !head.done;
},
() => {
index += 1
return fn(head.value, index-1)
}
);
}
// Calls the function with each of the items in turn, N at a time.
// It does not alter the items array
static forEachWithConstrainedParallelism<T, U>(items: Array<T>,
asynchCount: number,
fn: (item: T) => Promise<unknown>): Promise<unknown> {
if (asynchCount < 2 || items.length < asynchCount) {
return HigherOrderPromise.forEach(items, fn);
}
const copy: Array<T> = Array.from(items);
const workers: Array<any> = [];
for (let i = 0; i < asynchCount; i++) {
workers.push(forEachWorker(copy, fn));
}
return Promise
.all(workers)
.then(arrays => arrays.reduceRight((a, b) => a.concat(b), []));
}
// Calls the function with each of the items in turn, one at a time,
// adding the result to the returned array in the same order.
// Returns a mapped array
// It does not alter the items array (and assumes that it doesn't during execution
static map<T, U>(items: Array<T>,
fn: (item: T) => Promise<U>): Promise<Array<U>> {
const results: Array<U> = [];
return HigherOrderPromise.for(0, items.length, 1,
i => fn(items[i]).then(result => results.push(result)))
.then(() => results);
}
// Calls the function with each of the items in turn, one at a time,
// concatenating the result to the returned array in the same order.
// Returns a flat-mapped array
// It does not alter the items array (and assumes that it doesn't during execution
static flatMap<T, U>(items: Array<T>,
fn: (item: T) => Promise<Array<U>>): Promise<Array<U>> {
let results: Array<U> = [];
return HigherOrderPromise.for(0, items.length, 1,
i => fn(items[i]).then(result => results = results.concat(result)))
.then(() => results);
}
// Calls the predicate function with each of the items in turn, one at a time to select.
// Returns a filtered array
// It does not alter the items array (and assumes that it doesn't during execution
static filter<T>(items: Array<T>,
fn: (item: T) => Promise<boolean>): Promise<Array<T>> {
const results: Array<T> = [];
return HigherOrderPromise.for(0, items.length, 1,
i => fn(items[i]).then(result => {
if (result) {
results.push(items[i]);
}
}))
.then(() => results);
}
}
const whileLocal = (fnContinue: () => boolean,
fn: () => Promise<any>,
promise: DeferredPromise<any>) => {
setImmediate(() => {
try {
if (fnContinue()) {
fn()
.then(() => whileLocal(fnContinue, fn, promise))
.catch(e => promise.reject(e));
} else {
promise.resolve(undefined);
}
} catch (e) {
promise.reject(e);
}
});
}
const forEachWorker = <T, U>(items: Array<T>,
fn: (item: T) => Promise<unknown>): Promise<unknown> => {
if (items.length == 0) {
return Promise.resolve();
}
const head = items.shift();
try {
return fn(head!)
.then(() => forEachWorker(items, fn));
} catch (e) {
return Promise.reject(e);
}
}