Skip to content

Commit

Permalink
feat: add timeout parameter & fix code
Browse files Browse the repository at this point in the history
  • Loading branch information
doublelam committed Dec 23, 2024
1 parent e647777 commit 4bf3f8d
Show file tree
Hide file tree
Showing 11 changed files with 399 additions and 132 deletions.
69 changes: 52 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,24 @@ yarn add promises-delivery
```

## Usage
*index.js*
*index.ts*
```ts
import Delivery from 'promises-delivery';

const delivery = new Delivery<string>();
[1,2,3,4,5,6,7,8,9,10].forEach(async v => {
// Register a promise by giving a key. it will return a promise.
const val = await delivery.register(`key-${v}`);
console.log('------',`key-${v}`, val);
})
```
*where-else.js*
```ts
// pass delivery from outside
[1,2,3,4,5,6,7,8,9,10].forEach(v => {
setTimeout(() => {
// resolve a promise by calling `resolve` with a key.
delivery.resolve(`key-${v}`, `Key: key-${v} resolved`)
}, 1000 * v)
});
// Initialize with default timeout
const delivery = new Delivery<string>({ timeout: 1000 });

// Register a promise with custom timeout
const promise = delivery.register('uniqueKey', { timeout: 2000 });

// Resolve the promise later
delivery.resolve('uniqueKey', 'value');

// Or reject it
delivery.reject('uniqueKey', new Error('something went wrong'));

// Get an existing promise
const existingPromise = delivery.getPromise('uniqueKey');
```

## Methods
Expand All @@ -40,6 +38,43 @@ const delivery = new Delivery<string>();
}
```

## Error Handling

The library provides a custom `DeliveryError` class and error codes for different scenarios:

```typescript
import Delivery, { DeliveryError, DeliveryErrorCode, isDeliveryError } from 'promises-delivery';

const delivery = new Delivery({ timeout: 1000 });

try {
await delivery.register('key');
// or delivery.resolve(key, value);
// or delivery.reject(key, value);
} catch (error) {
if (isDeliveryError(error)) {
switch (error.code) {
case DeliveryErrorCode.Timeout:
console.log('Promise timed out');
break;
case DeliveryErrorCode.PromiseNotFound:
console.log('Promise not found');
break;
case DeliveryErrorCode.PromiseAlreadyRegistered:
console.log('Promise already registered');
break;
}
}
}
```

## Error Types
| Error Code | Description |
| --- | ----------- |
| `TIMEOUT` | Promise execution exceeded timeout limit |
| `PROMISE_NOT_FOUND` | Attempted to access a non-existent promise |
| `PROMISE_ALREADY_REGISTERED` | Attempted to register a duplicate key |

## Contribution

```bash
Expand Down
31 changes: 31 additions & 0 deletions dist/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export var DeliveryErrorCode;
(function (DeliveryErrorCode) {
DeliveryErrorCode["Timeout"] = "TIMEOUT";
DeliveryErrorCode["PromiseNotFound"] = "PROMISE_NOT_FOUND";
DeliveryErrorCode["PromiseAlreadyRegistered"] = "PROMISE_ALREADY_REGISTERED";
})(DeliveryErrorCode || (DeliveryErrorCode = {}));
export class DeliveryError extends Error {
code;
constructor(code, message) {
super(message);
this.code = code;
this.name = 'DeliveryError';
}
/**
* A static method to check if a given error is a `DeliveryError` and if its `code` property is valid.
*
* @param error - The error to be checked.
* @returns `true` if the error is a `DeliveryError` and its `code` property is valid; otherwise, `false`.
*
* @example
* ```typescript
* const error = new DeliveryError(DeliveryErrorCode.Timeout, 'Request timed out');
* if (DeliveryError.is(error)) {
* console.log(`Error code: ${error.code}`);
* }
* ```
*/
static is(error) {
return error instanceof DeliveryError;
}
}
74 changes: 42 additions & 32 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,90 @@
import { DeliveryError, DeliveryErrorCode } from './errors.js';
export const isDeliveryError = (error) => {
return DeliveryError.is(error);
};
export { DeliveryError, DeliveryErrorCode };
class Delivery {
promises = {};
timeout;
constructor(options) {
if (options && options.timeout) {
this.timeout = options.timeout;
}
}
/**
* Registers a new promise with the given key.
*
* This method creates a new promise and stores it along with its resolve and reject functions.
* If a promise with the given key already exists, an error is thrown.
*
* @param key - The unique identifier for the promise to be registered.
* @param options - Optional configuration for this specific promise.
* @param options.timeout - Override default timeout for this promise.
* @throws Will throw an error if a promise with the given key is already registered.
* @returns A Promise<T> that can be used to handle the asynchronous operation.
*/
register(key) {
register(key, options) {
if (this.promises[key]) {
throw new Error(`Promise with Key: ${key} is already registered`);
throw new DeliveryError(DeliveryErrorCode.PromiseAlreadyRegistered, `Promise with Key: ${key} is already registered`);
}
let timeoutId;
let resolvePromise;
let rejectPromise;
const promise = new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
const timeoutValue = options?.timeout ?? this.timeout;
if (timeoutValue) {
timeoutId = setTimeout(() => {
reject(new DeliveryError(DeliveryErrorCode.Timeout, `Promise with Key: ${key} timed out after ${timeoutValue}ms`));
delete this.promises[key];
}, timeoutValue);
}
});
const state = {
promise: null,
resolve: null,
reject: null,
promise,
resolve: resolvePromise,
reject: rejectPromise,
};
state.promise = new Promise((resolve, reject) => {
state.resolve = resolve;
state.reject = reject;
});
promise.finally(() => clearTimeout(timeoutId));
this.promises[key] = state;
return this.promises[key].promise;
return promise;
}
/**
* Resolves the promise associated with the given key with the provided value.
* Resolves the promise associated with the given key.
*
* @param key - The unique identifier for the promise to be resolved.
* @param value - The value to fulfill the promise with.
*
* @param value - The value to resolve the promise with.
* @throws Will throw an error if a promise with the given key is not found.
*
* @returns {void}
*/
resolve(key, value) {
if (!this.promises[key]) {
throw new Error(`Promise with Key: ${key} is not found`);
throw new DeliveryError(DeliveryErrorCode.PromiseNotFound, `Promise with Key: ${key} is not found`);
}
this.promises[key].resolve(value);
delete this.promises[key];
}
/**
* Rejects the promise associated with the given key with the provided reason.
*
* This method finds the promise with the specified key, rejects it with the given reason,
* and then removes it from the internal promises storage.
*
* @param key - The unique identifier for the promise to be rejected.
* @param reason - The reason for rejecting the promise.
* @throws Will throw an error if a promise with the given key is not found.
* @returns {void}
*/
reject(key, reason) {
if (!this.promises[key]) {
throw new Error(`Promise with Key: ${key} is not found`);
throw new DeliveryError(DeliveryErrorCode.PromiseNotFound, `Promise with Key: ${key} is not found`);
}
this.promises[key].reject(reason);
delete this.promises[key];
}
/**
* Returns the promise associated with the given key.
*
* This method finds the promise with the specified key and returns it.
* Retrieves the promise associated with the given key.
*
* @param key - The unique identifier for the promise to be retrieved.
* @throws Will throw an error if a promise with the given key is not found.
* @returns {Promise<T>} The promise associated with the given key.
* @param key - The unique identifier of the promise to be retrieved.
* @returns The promise associated with the given key if it exists, otherwise undefined.
*/
getPromise(key) {
if (!this.promises[key]) {
throw new Error(`Promise with Key: ${key} is not found`);
}
return this.promises[key].promise;
return this.promises[key]?.promise;
}
}
export default Delivery;
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "promises-delivery",
"description": "This package is used to manage multiple promises by giving a key, you can call resolve or reject function out of the promise parameter callback and manage them by a key.",
"version": "1.0.1",
"version": "1.1.0",
"main": "./dist/index.js",
"scripts": {
"test": "mocha"
"test": "mocha",
"build": "tsc && tsc-alias"
},
"type": "module",
"repository": "https://github.com/doublelam/promises-delivery",
Expand All @@ -23,6 +24,7 @@
"eslint-plugin-prettier": "5.2.1",
"mocha": "11.0.1",
"prettier": "3.3.3",
"tsc-alias": "1.8.10",
"typescript": "5.5.4"
},
"license": "MIT",
Expand Down
33 changes: 33 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export enum DeliveryErrorCode {
Timeout = 'TIMEOUT',
PromiseNotFound = 'PROMISE_NOT_FOUND',
PromiseAlreadyRegistered = 'PROMISE_ALREADY_REGISTERED',
}

export class DeliveryError extends Error {
constructor(
public code: DeliveryErrorCode,
message: string,
) {
super(message);
this.name = 'DeliveryError';
}

/**
* A static method to check if a given error is a `DeliveryError` and if its `code` property is valid.
*
* @param error - The error to be checked.
* @returns `true` if the error is a `DeliveryError` and its `code` property is valid; otherwise, `false`.
*
* @example
* ```typescript
* const error = new DeliveryError(DeliveryErrorCode.Timeout, 'Request timed out');
* if (DeliveryError.is(error)) {
* console.log(`Error code: ${error.code}`);
* }
* ```
*/
static is(error: unknown): error is DeliveryError {
return error instanceof DeliveryError;
}
}
Loading

0 comments on commit 4bf3f8d

Please sign in to comment.