Skip to content

Relign is a little utility library for processing data and tasks in concurrent or serial patterns

License

Notifications You must be signed in to change notification settings

RobertWHurst/Relign

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Relign

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.

Installation

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);

A Note on Execution

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.

Function Index

Parallel

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 }

Parallel Limit

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 }

Series

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 }

Auto

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 }

Parallel Map

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 }

Parallel Map Limit

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 }

Series Map

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 }

Parallel Filter

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 }

Parallel Filter Limit

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 }

Series Filter

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 }

Parallel Find

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

Parallel Find Limit

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

Series Find

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

Parallel Flat Map

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]

Parallel Flat Map Limit

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]

Series Flat Map

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]

Series Reduce

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

Next Tick

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'

Set Timeout

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'

Set Interval

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

Callback to Promise

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

Execute

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'

Help Welcome

If you want to support this project by throwing be some coffee money It's greatly appreciated.

sponsor

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!

About

Relign is a little utility library for processing data and tasks in concurrent or serial patterns

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project