If you encounter a bug please report it.
Relign is a little utility library for processing data and tasks in concurrent or serial patterns. It's heavily inspired by the wonderful library async which was widly used back in the bad old days of callback patterns popular in node programs at the time. Relign is for modern TypeScript and JavaScript programs that use async functions and promises. It's not just an alternative to Promise.all but an advanced utility library that fills the gaps in asynchronous patterns, making them more developer-friendly and maintainable.
Relign is a collection of functions that take tasks or data and process them in various ways. These functions are all designed to be simple and easy to use without confusion.
Relign is published on NPM. It can be used in any TypeScript or JavaScript project, and is esm or cjs compatible.
npm i relign
import { parallelMapLimit } from 'relign';
const urlsToFetch: string[] = [
'https://coolsite.com/path/one',
'https://coolsite.com/path/two',
'https://coolsite.com/path/three',
//...
'https://coolsite.com/path/oneThousand',
]
const resultJson = await parallelMapLimit(urlsToFetch, async url => {
const response = await fetch(url);
return response.json();
}, 10);
One of the main things that make relign special is how it handles data or tasks.
Relign will happily accept any value as a task. If the value is a function it will be executed. If the value is a promise it will be awaited. If the value is a function that returns a promise, the promise will be awaited. If the value is anything else it will be returned as is. This means that you can pass a mix of functions, promises, and values to any relign function accepting tasks and it will do the right thing.
Item functions work in a similar way, but instead these functions take a worker function that is called for each item in the collection. This function is executed in the same way as tasks. That means if the worker returns a promise, the promise will be awaited. If the worker returns a value that value will be returned as is.
- parallel
- parallelLimit
- series
- auto
- parallelMap
- parallelMapLimit
- seriesMap
- parallelFilter
- parallelFilterLimit
- seriesFilter
- parallelFind
- parallelFindLimit
- seriesFind
- parallelFlatMap
- parallelFlatMapLimit
- seriesFlatMap
- seriesReduce
- nextTick
- setTimeout
- setInterval
- cbToPromise
- exec
The parallel
function takes a collection of tasks and executes them concurrently.
It returns a promise that resolves to an array of the results of each task. It
will happily accept an array or object of tasks. Tasks can be functions, promises,
functions that return a promise, or any other value.
With an Array
import { parallel } from 'relign';
const results = await parallel([
1,
Promise.resolve(2),
async () => 3,
async () => Promise.resolve(4),
])
console.log(results); // [1, 2, 3, 4]
With an Object
import { parallel } from 'relign';
const results = await parallel({
one: 1,
two: Promise.resolve(2),
three: async () => 3,
four: async () => Promise.resolve(4),
})
console.log(results); // { one: 1, two: 2, three: 3, four: 4 }
The parallelLimit
function takes a collection of tasks and executes them
concurrently with a limit on the number of tasks that can be executed at once.
It returns a promise that resolves to an array of the results of each task. It
will happily accept an array or object of tasks. Tasks can be functions, promises,
functions that return a promise, or any other value.
With an Array
import { parallelLimit } from 'relign';
const results = await parallelLimit([
1,
Promise.resolve(2),
async () => 3,
async () => Promise.resolve(4),
], 2)
console.log(results); // [1, 2, 3, 4]
With an Object
import { parallelLimit } from 'relign';
const results = await parallelLimit({
one: 1,
two: Promise.resolve(2),
three: async () => 3,
four: async () => Promise.resolve(4),
}, 2)
console.log(results); // { one: 1, two: 2, three: 3, four: 4 }
The series
function takes a collection of tasks and executes them in series.
It returns a promise that resolves to an array of the results of each task. It
will happily accept an array or object of tasks. Tasks can be functions, promises,
functions that return a promise, or any other value.
With an Array
import { series } from 'relign';
const results = await series([
1,
Promise.resolve(2),
async () => 3,
async () => Promise.resolve(4),
])
console.log(results); // [1, 2, 3, 4]
With an Object
import { series } from 'relign';
const results = await series({
one: 1,
two: Promise.resolve(2),
three: async () => 3,
four: async () => Promise.resolve(4),
})
console.log(results); // { one: 1, two: 2, three: 3, four: 4 }
The auto
function is great when you want tasks to be executed in a specific
order based on dependencies on one another, but still want to run non interdependent
tasks concurrently. It takes an object with a key for each task and a array value
of dependencies and a task. It returns a promise that resolves to an object with
the results of each task. Tasks can be functions, promises, functions that return
promises, or any other value.
import { auto } from 'relign';
const results = await auto({
one: [1],
two: [Promise.resolve(2)],
three: ['one', 'two', async ({ one, two }) => one + two],
four: ['two', async ({ two }) => two * 2],
})
console.log(results); // { one: 1, two: 2, three: 3, four: 4 }
The parallelMap
function takes a collection of items and a map function. It
will then concurrently process each item in the collection with the map function.
Note that the map function can return a promise or any other value. If a promise
is returned, the result of the promise will be used as the value for the item in
the results array. If any other value is returned, that value will be used as is.
With an Array
import { parallelMap } from 'relign';
const results = await parallelMap([1, 2, 3, 4], async (item) => {
return item * 2;
})
console.log(results); // [2, 4, 6, 8]
With an Object
import { parallelMap } from 'relign';
const results = await parallelMap({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item * 2;
})
console.log(results); // { one: 2, two: 4, three: 6, four: 8 }
The parallelMapLimit
function takes a collection of items, a map function, and
a limit. It will then concurrently process each item in the collection with the
map function with a limit on the number of items that can be processed at once.
Note that the map function can return a promise or any other value. If a promise
is returned, the result of the promise will be used as the value for the item in
the results array. If any other value is returned, that value will be used as is.
With an Array
import { parallelMapLimit } from 'relign';
const results = await parallelMapLimit([1, 2, 3, 4], async (item) => {
return item * 2;
}, 2)
console.log(results); // [2, 4, 6, 8]
With an Object
import { parallelMapLimit } from 'relign';
const results = await parallelMapLimit({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item * 2;
}, 2)
console.log(results); // { one: 2, two: 4, three: 6, four: 8 }
The seriesMap
function takes a collection of items and a map function. It
will then process each item in the collection with the map function in series.
Note that the map function can return a promise or any other value. If a promise
is returned, the result of the promise will be used as the value for the item in
the results array. If any other value is returned, that value will be used as is.
With an Array
import { seriesMap } from 'relign';
const results = await seriesMap([1, 2, 3, 4], async (item) => {
return item * 2;
})
console.log(results); // [2, 4, 6, 8]
With an Object
import { seriesMap } from 'relign';
const results = await seriesMap({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item * 2;
})
console.log(results); // { one: 2, two: 4, three: 6, four: 8 }
The parallelFilter
function takes a collection of items and a filter function.
It will then concurrently process each item in the collection with the filter
function. Note that the filter function can return a promise that resolves to a
boolean or a boolean. The boolean will be used to determine if the item should
be included in the results.
With an Array
import { parallelFilter } from 'relign';
const results = await parallelFilter([1, 2, 3, 4], async (item) => {
return item % 2 === 0;
})
console.log(results); // [2, 4]
With an Object
import { parallelFilter } from 'relign';
const results = await parallelFilter({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item % 2 === 0;
})
console.log(results); // { two: 2, four: 4 }
The parallelFilterLimit
function takes a collection of items, a filter
function, and a limit. It will then concurrently process each item in the
collection with the filter function with a limit on the number of items that can
be processed at once. Note that the filter function can return a promise that
resolves to a boolean or a boolean. The boolean will be used to determine if the
item should be included in the results.
With an Array
import { parallelFilterLimit } from 'relign';
const results = await parallelFilterLimit([1, 2, 3, 4], async (item) => {
return item % 2 === 0;
}, 2)
console.log(results); // [2, 4]
With an Object
import { parallelFilterLimit } from 'relign';
const results = await parallelFilterLimit({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item % 2 === 0;
}, 2)
console.log(results); // { two: 2, four: 4 }
The seriesFilter
function takes a collection of items and a filter function.
It will then process each item in the collection with the filter function in
series. Note that the filter function can return a promise that resolves to a
boolean or a boolean. The boolean will be used to determine if the item should
be included in the results.
With an Array
import { seriesFilter } from 'relign';
const results = await seriesFilter([1, 2, 3, 4], async (item) => {
return item % 2 === 0;
})
console.log(results); // [2, 4]
With an Object
import { seriesFilter } from 'relign';
const results = await seriesFilter({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item % 2 === 0;
})
console.log(results); // { two: 2, four: 4 }
The parallelFind
function takes a collection of items and a find function. It
will then concurrently execute the find function on each item in the collection
until the find function returns a promise that resolves to true, or true. The
first item to result in true being returned or resolved will be returned as the
result. If no item results in true, undefined will be returned.
With an Array
import { parallelFind } from 'relign';
const result = await parallelFind([1, 2, 3, 4], async (item) => {
return item === 3;
})
console.log(result); // 3
With an Object
import { parallelFind } from 'relign';
const result = await parallelFind({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item === 3;
})
console.log(result); // 3
The parallelFindLimit
function takes a collection of items, a find function,
and a limit. It will then concurrently execute the find function on each item in
the collection with a limit on the number of items that can be processed at
once. The find function will be executed on each item in the collection until
the find function returns a promise that resolves to true, or true. The first
item to result in true being returned or resolved will be returned as the
result. If no item results in true, undefined will be returned.
With an Array
import { parallelFindLimit } from 'relign';
const result = await parallelFindLimit([1, 2, 3, 4], async (item) => {
return item === 3;
}, 2)
console.log(result); // 3
With an Object
import { parallelFindLimit } from 'relign';
const result = await parallelFindLimit({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item === 3;
}, 2)
console.log(result); // 3
The seriesFind
function takes a collection of items and a find function. It
will then execute the find function on each item in the collection in series
until the find function returns a promise that resolves to true, or true. The
first item to result in true being returned or resolved will be returned as the
result. If no item results in true, undefined will be returned.
With an Array
import { seriesFind } from 'relign';
const result = await seriesFind([1, 2, 3, 4], async (item) => {
return item === 3;
})
console.log(result); // 3
With an Object
import { seriesFind } from 'relign';
const result = await seriesFind({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return item === 3;
})
console.log(result); // 3
The parallelFlatMap
function takes a collection of items and a flat map function.
It will then concurrently execute the flat map function on each item in the
collection. Note that the flat map function can return a promise that resolves
to an array or an array. The array will be flattened into the result.
With an Array
import { parallelFlatMap } from 'relign';
const results = await parallelFlatMap([1, 2, 3, 4], async (item) => {
return [item, item];
})
console.log(results); // [1, 1, 2, 2, 3, 3, 4, 4]
With an Object
import { parallelFlatMap } from 'relign';
const results = await parallelFlatMap({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return [item, item];
})
console.log(results); // [1, 1, 2, 2, 3, 3, 4, 4]
The parallelFlatMapLimit
function takes a collection of items, a flat map
function, and a limit. It will then concurrently execute the flat map function
on each item in the collection with a limit on the number of items that can be
processed at once. Note that the flat map function can return a promise that
resolves to an array or an array. The array will be flattened into the result.
With an Array
import { parallelFlatMapLimit } from 'relign';
const results = await parallelFlatMapLimit([1, 2, 3, 4], async (item) => {
return [item, item];
}, 2)
console.log(results); // [1, 1, 2, 2, 3, 3, 4, 4]
With an Object
import { parallelFlatMapLimit } from 'relign';
const results = await parallelFlatMapLimit({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return [item, item];
}, 2)
console.log(results); // [1, 1, 2, 2, 3, 3, 4, 4]
The seriesFlatMap
function takes a collection of items and a flat map
function. It will then execute the flat map function on each item in the
collection in series. Note that the flat map function can return a promise that
resolves to an array or an array. The array will be flattened into the result.
With an Array
import { seriesFlatMap } from 'relign';
const results = await seriesFlatMap([1, 2, 3, 4], async (item) => {
return [item, item];
})
console.log(results); // [1, 1, 2, 2, 3, 3, 4, 4]
With an Object
import { seriesFlatMap } from 'relign';
const results = await seriesFlatMap({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (item) => {
return [item, item];
})
console.log(results); // [1, 1, 2, 2, 3, 3, 4, 4]
The seriesReduce
function takes a collection of items, a reducer function,
and an initial value. It will then execute the reducer function on each item in
the collection in series. The result of the reducer function can be a promise
or any value. If the reducer function returns a promise, the result of the
promise will be used as the memo for the next iteration, otherwise
the return value will be used as the memo. After all items have been processed,
the memo will be returned as the result.
With an Array
import { seriesReduce } from 'relign';
const result = await seriesReduce([1, 2, 3, 4], async (memo, item) => {
return memo + item;
}, 0)
console.log(result); // 10
With an Object
import { seriesReduce } from 'relign';
const result = await seriesReduce({
one: 1,
two: 2,
three: 3,
four: 4,
}, async (memo, item) => {
return memo + item;
}, 0)
console.log(result); // 10
The nextTick
function returns a promise that resolves on the next tick of the
event loop. If used in a browser, requestAnimationFrame under the hood instead
of node's process.nextTick.
import { nextTick } from 'relign';
await nextTick();
Next tick can also take a task or value as an argument. If a task is passed, the task will be executed on the next tick of the event loop and the promise will resolve to the result of the task. If a value is passed, the promise will resolve to the value on the next tick of the event loop.
import { nextTick } from 'relign';
const result = await nextTick(() => {
return 'hello world';
});
console.log(result); // 'hello world'
The setTimeout
takes a delay and returns a promise that resolves after the
delay has elapsed.
import { setTimeout } from 'relign';
await setTimeout(1000);
Set timeout can also take a task or value as an argument. If a task is passed, the task will be executed after the delay has elapsed and the promise will resolve to the result of the task. If a value is passed, the promise will resolve to the value after the delay has elapsed.
import { setTimeout } from 'relign';
const result = await setTimeout(1000, () => {
return 'hello world';
});
console.log(result); // 'hello world'
The setInterval
takes a task and a interval duration. It will call the task
every interval period until the task throws, or executes the resolve function
passed to it. The promise returned by setInterval will not resolve until the
task throws or executes the resolve function passed to it. If the task calls
the resolve function and passes a value, the promise will resolve to that
value.
import { setInterval } from 'relign';
let count = 0;
const result = await setInterval(1000, (resolve) => {
count += 1;
if (count === 5) {
resolve(count);
}
});
console.log(result); // 5
The cbToPromise
function takes a function that uses the error first callback
pattern and returns a function that returns a promise. The returned function
will call the original function and resolve or reject the promise based on the
error first callback.
import { cbToPromise } from 'relign';
const readFile = cbToPromise(fs.readFile);
const result = await readFile('./package.json', 'utf8');
console.log(result); // package.json contents
The exec
function is the heart of relign and is used by a majority of the
functions above. The execute function takes a task or value and executes it.
If the task is a function, it will be called and the result will be returned.
If the task is a promise, the promise will be awaited and the result will be
returned. If the task is any other value, the value will be returned.
import { exec } from 'relign';
const result = exec(() => {
return 'hello world';
});
console.log(result); // 'hello world'
If you want to support this project by throwing be some coffee money It's greatly appreciated.
If your interested in providing feedback or would like to contribute please feel free to do so. I recommend first opening an issue expressing your feedback or intent to contribute a change, from there we can consider your feedback or guide your contribution efforts. Any and all help is greatly appreciated since this is an open source effort after all.
Thank you!