Skip to content

Commit

Permalink
feat: Enhanced DX by including thenable response and curry function (#13
Browse files Browse the repository at this point in the history
)
  • Loading branch information
fahimfaisaal authored Jan 29, 2024
1 parent 39ccb88 commit e69672b
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 128 deletions.
11 changes: 11 additions & 0 deletions .changeset/strange-planets-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"settle-map": major
---

# Update: Enhanced Response Handling

- Removed the `all` getter in this release. With `settleMap` now returning a `Thenable` object, you can use the more intuitive `await` syntax without `all`.
- Added a new `omitResult` option to prevent result storage. This is useful for saving memory when using the event-driven `on` function or when you would like to ignore it.
- Implemented function overriding to manage the map function response based on the `omitResult` value.
- Renamed the `stop` function to `abort` for clarity.
- Introduced a new curry function, `createSettleMap`, to simplify code and adhere to the `DRY` (Don't Repeat Yourself) principle.
4 changes: 3 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
src
test

.changeset
.github
.nvmrc

pnpm-lock.yaml
tsconfig.json
vit.config.ts

37 changes: 23 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/fahimfaisaal/settle-map/.github%2Fworkflows%2Fmain.yml?branch=main&style=flat&logo=github-actions&label=CI) ![NPM Downloads](https://img.shields.io/npm/dm/settle-map?style=flat&logo=npm&logoColor=red&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fsettle-map) ![GitHub Repo stars](https://img.shields.io/github/stars/fahimfaisaal/settle-map?style=flat&logo=github&link=https%3A%2F%2Fgithub.com%2Ffahimfaisaal%2Fsettle-map)

Settle Map is a tool that combines the features of `Promise.allSettled` and `Array.map`. It simplifies the process of mapping promises, providing results flexibility and lets you control how many can run at the same time using concurrency. In other words, it will help you prevent being rate-limited.
Settle Map is a tool that combines the features of `Promise.allSettled` and `Array.map`. It simplifies the process of mapping promises, providing results flexibility with hassle free error handling and lets you control how many can run at the same time using concurrency. In other words, it will help you prevent being rate-limited.

## ⚙️ Installation

Expand Down Expand Up @@ -34,7 +34,7 @@ const settled = settleMap(
Get response value on resolve any item immediately

```ts
settled.on("resolve", ({ value, index }) => {
settled.on("resolve", ({ value, item, index }) => {
// your actions
});
```
Expand All @@ -50,13 +50,15 @@ settled.on("reject", ({ error, item, index }) => {
Get response immediately on retry any item

```ts
settled.on("retry", ({ error, retry, item, index }) => {
settled.on("retry", ({ error, item, index, tried }) => {
// your actions
});
```

Get the final result object

> The `complete` event will not be emitted when `omitResult` options will be `true`.
```ts
settled.on("complete", ({ values, error }) => {
// your actions
Expand All @@ -68,11 +70,11 @@ settled.on("complete", ({ values, error }) => {
### Get the final result at once

To wait and get all result at once an `all` getter return you the universal resolved promise
To wait and get all result at once you just have to add `await` keyword like casual `async/await` approach or invoke the `then` method.

```ts
const result = await settled.all; // An universal resolved promise
console.log(result);
const result = await settled; // An universal promise like syntax that returns only resolved response

/* output
{
values: [1, 3, 5],
Expand All @@ -83,18 +85,18 @@ console.log(result);

### Wait Until All Processes Are Done

If you want to wait until all processes are done, you can use the `.waitUntilFinished` method.
If you want to wait until all processes are done, you can use the `waitUntilFinished` method.

```ts
await settled.waitUntilFinished(); // will return always a resolved promise
```

### Stop all process when you want
### Abort all process when you need

you could stop the all process any time and get the current result using `stop` method
you could abort the all remaining processes any time and get the current result using `abort` method

```ts
const result = settled.stop();
const result = settled.abort();
```

### Get real time status
Expand Down Expand Up @@ -124,6 +126,7 @@ const options = {
attempts: 3, // the number of attempts on fail
delay: 2000, // ms
},
omitResult: true, // the final result will be undefined incase it's true.
};
const settled = settleMap(items, asyncMapFunction, options);
```
Expand All @@ -138,17 +141,23 @@ A function that settles promises returned by the provided function (`fn`) for ea

- `items` (`T[]`): An array of items to be processed.
- `fn` (`(item: T, index: number) => Promise<R>`): A function that takes an item and its index as parameters and returns a Promise.
- `options` (`SettleOptions | number`): An object that specifies the concurrency and retry options. If a number is provided, it is treated as the concurrency level. default option is `1`
- `options` (`SettleOptions | number`): An object that specifies the concurrency and retry options. If a number is provided, it is treated as the concurrency level. Default is `1`

#### Return Value

Returns an object with the following properties and methods:
Returns an object with the following methods:

- `all`: A promise that resolves when all items have been processed and return the resolved and rejects items in a single object.
- `waitUntilFinished()`: A method that returns a promise that resolves when all items have been processed, regardless of whether they succeeded or failed.
- `status()`: A method that returns the current status of the settling process.
- `on(event, listener)`: A method that allows you to listen for certain events.
- `stop()`: A method that stops the settling process and return the current result.
- `abort()`: A method that aborts all remain processes and return the current result.

> [!NOTE]
> Since the return value has `then` method so the `settleMap` function is awaitable. Followed the `Thenable` approach. [see MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables)
### `createSettleMap(options)`

A curry function of settleMap that will help you to set default options and use the map function with same option everywhere. even you could modify the options for each individual use.

## 👤 Author (Fahim Faisaal)

Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
"typescript": "^5.3.3",
"vitest": "^1.2.0"
},
"engineStrict": true,
"engines": {
"node": ">=16.14.0"
},
"keywords": [
"map",
"promise",
Expand Down
18 changes: 18 additions & 0 deletions src/create-settle-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import settleMap from ".";
import type { SettleOptions } from "./types";
import { mergeOptions } from "./utils";

const createSettleMap =
(defaultOption: SettleOptions) =>
<T, R>(
items: T[],
fn: (item: T, index: number) => Promise<R>,
newOption?: number | Partial<SettleOptions>
) =>
settleMap(
items,
fn,
mergeOptions(newOption ?? defaultOption, defaultOption)
);

export default createSettleMap;
28 changes: 24 additions & 4 deletions src/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import type { Emit, Listener } from "./types";
import type { Listener } from "./types";

class EventEmitter<T, R> {
public readonly listeners: Listener<T, R> = {};
public listeners: Listener<T, R> | null = null;

public readonly emit: Emit<T, R> = (event, args) => {
constructor() {
this.listeners = {};
}

public readonly emit = <E extends keyof Listener<T, R>>(
event: E,
args: Parameters<NonNullable<Listener<T, R>[E]>>[0]
) => {
try {
return this.listeners[event]?.(args as never);
return this.listeners?.[event]?.(args as never);
} catch {
return null;
}
};

public on<E extends keyof Listener<T, R>>(
event: E,
listener: (payload: Parameters<NonNullable<Listener<T, R>[E]>>[0]) => void
) {
if (!this.listeners) return;

this.listeners[event] = listener;
}

public destroy() {
this.listeners = null;
}
}

export default EventEmitter;
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import settleMap from "./settle-map";
import createSettleMap from "./create-settle-map";

export { settleMap };
export { settleMap, createSettleMap };

export default settleMap;
50 changes: 40 additions & 10 deletions src/settle-map.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
import type { SettleOptions } from "./types";
import type { ReturnObjectType, Result, SettleOptions } from "./types";
import { mergeOptions } from "./utils";
import Settler from "./settler";

const settleMap = <T, R>(
// return void incase omitResult is true
function settleMap<T, R>(
items: T[],
fn: (item: T, index: number) => Promise<R>,
options: SettleOptions & { omitResult: true }
): ReturnObjectType<T, R> & {
then: (onfulfilled?: (value: undefined) => unknown) => Promise<unknown>;
abort: () => undefined;
};

// return result object incase omitResult is false or undefined
function settleMap<T, R>(
items: T[],
fn: (item: T, index: number) => Promise<R>,
options?: number | SettleOptions
): ReturnObjectType<T, R> & {
then: (onfulfilled?: (value: Result<T, R>) => unknown) => Promise<unknown>;
abort: () => Result<T, R>;
};

function settleMap<T, R>(
items: T[],
fn: (item: T, index: number) => Promise<R>,
options: SettleOptions | number = 1
) => {
) {
const settler = new Settler<T, R>(mergeOptions(options));
const promise = settler.settle(items, fn);

return {
get all() {
return promise;
waitUntilFinished: async () => {
await settler.promise;
},
waitUntilFinished: () => settler.waitUntilFinished(),
status() {
return settler.status;
return {
activeCount: settler.limit.activeCount,
pendingCount: settler.limit.pendingCount,
};
},
on: settler.events.on.bind(settler.events),
abort: () => {
settler.limit.clearQueue();
settler.events.destroy();

return settler.result;
},
then(onfulfilled?: (value: Result<T, R> | undefined) => unknown) {
return promise.then(onfulfilled);
},
on: settler.on.bind(settler),
stop: () => settler.stop(),
};
};
}

export default settleMap;
Loading

0 comments on commit e69672b

Please sign in to comment.