seq-prom
Version:
A small library to allow for sequential operations on an array using Promises
512 lines (420 loc) • 13.5 kB
Markdown
Type-safe promise processing for arrays in TypeScript/JavaScript
```bash
npm install seq-prom --save
```
```bash
pnpm add seq-prom
```
| Option | Type | Description | Default | Required? |
|-----------|----------|--------------------------------------------------------------------------------------------------|---------|-----------|
| list | Array<T> | List of items to iterate through | | Yes |
| cb | Function | Type-safe callback function for processing each item | | Yes |
| size | Integer | Size of the batch or pool (number of concurrent operations) | 1 | No |
| errorCB | Function | Called when there is an error, with the item and reason for error | | No |
| finalCB | Function | Called when all is done. Passes in a list of errors, and any items, passed to the resolve method | | No |
| useBatch | Boolean | Process items in batches of size `size` | false | No |
| autoStart | Boolean | Instead of having to call .start() will do this for you | false | No |
## Core Concepts
### Processing Modes
SeqProm supports two processing modes for handling collections of items:
### Pool Mode (Default)
In pool mode, SeqProm creates a "pool" of concurrent promises, processing items as previous ones complete. This is ideal for handling a large number of items with controlled concurrency.
- Set `poolSize` to control how many items process concurrently
- Items start processing immediately up to the pool size limit
- As each item completes, the next waiting item begins processing
- Order of completion depends on how long each item takes to process
### Batch Mode
In batch mode, SeqProm processes items in discrete batches of a specific size. Each batch completes fully before the next batch begins.
- Set `useBatch: true` and `size` to define batch size
- Items are processed in sequential batches
- All items in a batch are processed concurrently
- The next batch only starts after the current batch fully completes
- Useful for operations that should happen in specific groups
## Usage Examples
### Basic Usage
#### Using Direct Return Values (Simplest)
```typescript
import SeqProm from "seq-prom";
// Simply return a value from the callback
SeqProm({
list: [1, 2, 3],
autoStart: true,
cb(item) {
console.log(`Processing ${item}`);
return `Result: ${item * 2}`;
},
finalCB(errors, responses) {
console.log("All done!", responses);
}
});
```
```typescript
import SeqProm from "seq-prom";
// Asynchronous callback with async/await
SeqProm({
list: [1, 2, 3],
autoStart: true,
cb: async (item) => {
console.log(`Processing ${item}`);
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, 100));
return `Processed ${item}`;
},
finalCB(errors, responses) {
console.log("All results:", responses.map(r => r.result));
}
});
```
```typescript
import SeqProm from "seq-prom";
// Using explicit resolve/reject callbacks
// Type parameters are needed for this style
let seqProm = SeqProm<number, void>({
list: [1, 2, 3],
cb(item, {resolve, reject}) {
console.log(`Item [${item}] called!`);
setTimeout(function () {
if (item === 3) {
return reject("Not sure about this!");
} else {
return resolve();
}
}, item * 1000);
},
errorCB(item, reason) {
console.error(`Item [${item}] failed with error: ${reason}`);
},
finalCB() {
console.log("All done!");
}
});
seqProm.start();
```
```typescript
import SeqProm from "seq-prom";
// Processing with a pool of 2 concurrent operations using async/await
SeqProm({
list: [1, 2, 3, 4],
autoStart: true,
size: 2, // 2 concurrent operations at a time
cb: async (item) => {
console.log(`Processing item ${item}`);
// Simulate async operation
await new Promise(resolve => setTimeout(resolve, item * 200));
// If item is 3, throw an error (will be caught internally)
if (item === 3) {
throw new Error("Processing error");
}
return `Result: ${item * 10}`;
},
errorCB(item, reason) {
console.error(`Item [${item}] failed with error: ${reason}`);
},
finalCB(errors, responses) {
console.log("All items processed!");
console.log(`Successful: ${responses.length}, Failed: ${errors.length}`);
// Print successful results
responses.forEach(res => {
console.log(`Item ${res.item} → ${res.result}`);
});
}
});
// Use the promise for further actions
const [errors, responses] = await SeqProm({
list: [5, 6, 7],
size: 2,
cb: async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return num * 2;
}
}).start().promise;
console.log("All processing complete:", responses.map(r => r.result));
```
```typescript
import SeqProm from "seq-prom";
// Processing in batches using async/await
SeqProm({
list: [1, 2, 3, 4, 5, 6],
autoStart: true,
useBatch: true, // Enable batch processing mode
size: 2, // Process items in batches of 2
// Using async callback
cb: async (item) => {
console.log(`Batch processing item ${item}`);
try {
// Simulate database operation
await new Promise(resolve => setTimeout(resolve, 300));
// Each batch waits for all its items to complete before starting the next batch
return {
processed: true,
value: item * 10,
timestamp: new Date().toISOString()
};
} catch (error) {
// Error handling with async/await
console.error(`Error processing item ${item}:`, error);
throw error; // Re-throw to trigger error handling in SeqProm
}
},
finalCB(errors, responses) {
console.log(`Processed ${responses.length} items in ${Math.ceil(responses.length/2)} batches`);
// Group responses by batch (for demonstration purposes)
const batches = [];
for (let i = 0; i < responses.length; i += 2) {
batches.push(responses.slice(i, i + 2));
}
// Show each batch's results
batches.forEach((batch, i) => {
console.log(`Batch ${i+1} results:`);
batch.forEach(res => {
console.log(`- Item ${res.item} → ${res.result.value} at ${res.result.timestamp}`);
});
});
}
});
```
> **Note**: In batch mode, all items in a batch are processed concurrently, but the next batch only starts after the current batch completely finishes processing.
| Argument | Description |
|----------|--------------------------------------------------|
| item | Item from the list (type T) |
| extra | Object containing resolve, reject, and self |
| extra.resolve | The resolve function (accepts value of type RT) |
| extra.reject | The reject function for error handling |
| extra.self | Reference to the SeqProm instance (can call `.stop()`) |
### errorCB
| Argument | Description |
|----------|-----------------------------------------------------------------------|
| item | Item from the list |
| reason | Reason for the error, either from the reject method or a caught error |
### finalCB
| Argument | Description |
|-----------|---------------------------------------------------|
| error | List of errors that have occured |
| response | List of items passed back to the resolve function |
### Promise Handling
```typescript
import SeqProm from "seq-prom";
// Using the promise directly with typed response
SeqProm<number, string>({
list: [1, 2, 3],
autoStart: true,
cb(item, {resolve, reject}) {
console.log(`Item [${item}] called!`);
setTimeout(function () {
if (item === 3) {
return reject("Too large");
} else {
// Converting numbers to strings as our return type is string
return resolve(`Value: ${item}`);
}
}, item * 1000);
}
})
.promise
.then(([errors, responses]) => {
// errors: Array<{item: number, reason: string | Error}>
// responses: Array<{item: number, result: string}>
console.log(errors, responses);
// You can access the successful results
responses.forEach(response => {
console.log(`Item ${response.item} resulted in: ${response.result}`);
});
});
```
SeqProm is fully type-safe and supports generic type parameters for both input items and return values.
SeqProm takes two type parameters:
```typescript
SeqProm<ReturnType, ItemType>({ ... })
```
- `ReturnType` - The type of value returned by your callback function
- `ItemType` - The type of items in your input list
### Example with Complex Types
```typescript
// Define custom types
interface User {
id: number;
name: string;
}
interface ProcessedUser {
id: number;
displayName: string;
lastProcessed: Date;
}
// Using SeqProm with complex types
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
];
SeqProm<ProcessedUser, User>({
list: users,
size: 2, // Process 2 users concurrently
cb(user, {resolve}) {
// Transform User to ProcessedUser
const processed: ProcessedUser = {
id: user.id,
displayName: user.name.toUpperCase(),
lastProcessed: new Date()
};
resolve(processed);
},
finalCB(errors, processedUsers) {
// processedUsers is typed as Array<{item: User, result: ProcessedUser}>
processedUsers.forEach(item => {
console.log(
`User ${item.item.name} processed as ${item.result.displayName} at ${item.result.lastProcessed}`
);
});
}
}).start();
```
```typescript
// Simply return a value from the callback
SeqProm({
list: ['apple', 'banana', 'cherry'],
cb(item) {
// Return directly - SeqProm handles this automatically
return item.toUpperCase();
}
});
```
```typescript
// Using async/await for clean asynchronous code
SeqProm({
list: [1, 2, 3],
cb: async (item) => {
// Use await for any async operations
const result = await someAsyncOperation(item);
return result;
}
});
// Helper function
async function someAsyncOperation(value: number): Promise<string> {
return new Promise(resolve => {
setTimeout(() => resolve(`Processed ${value}`), 100);
});
}
```
```typescript
// Returning a Promise directly
SeqProm({
list: [1, 2, 3],
cb(item) {
return new Promise<string>(resolve => {
setTimeout(() => resolve(`Number: ${item}`), 100);
});
}
});
```
```typescript
// Using the provided resolve/reject functions
SeqProm<string, number>({
list: [1, 2, 3],
cb(item, {resolve, reject}) {
if (item > 2) {
reject('Value too large');
} else {
resolve(`Number: ${item}`);
}
}
});
```
```typescript
// Define our response type structure
interface ApiResponse<T> {
data: T;
timestamp: number;
success: boolean;
}
interface UserData {
id: number;
name: string;
email: string;
}
// Process a list of user IDs with complex Promise handling
SeqProm<ApiResponse<UserData>, number>({
list: [101, 102, 103],
size: 2, // Process 2 at a time
cb(userId) {
// Return a typed Promise chain
return fetchUserById(userId)
.then(userData => {
// Transform the data into our ApiResponse format
return {
data: userData,
timestamp: Date.now(),
success: true
};
})
.catch(error => {
// Handle errors in the Promise chain
console.error(`Failed to fetch user ${userId}:`, error);
throw new Error(`User fetch failed: ${error.message}`);
});
},
finalCB(errors, responses) {
// Type-safe access to the complex response structure
responses.forEach(res => {
const userData = res.result.data;
console.log(
`User ${userData.name} (${userData.email}) fetched at ${new Date(res.result.timestamp).toLocaleString()}`
);
});
}
});
// Mock API function
function fetchUserById(id: number): Promise<UserData> {
return new Promise((resolve, reject) => {
// Simulate API call
setTimeout(() => {
if (id === 102) {
reject(new Error("User not found"));
return;
}
resolve({
id,
name: `User ${id}`,
email: `user${id}@example.com`
});
}, 200);
});
}
```
```bash
npm test
```
* 3.0.0 ReadMe update
* 3.0.0 TypeScript update
* Full TypeScript rewrite with generic type support
* Enhanced error handling and promise support
* Renamed parameters for clarity (batchSize -> size, useStream -> useBatch)
* Improved test coverage and fixed Promise resolution issues
* Support for direct return values, Promises, and resolve/reject callbacks
* 1.1.1 Package update
* 1.1.0
* Updates to allow passage of data
* Added more tests
* Added autStart option
* Added ability to chain off of promise
* 1.0.0 Initial release