seq-prom
v3.0.2
Published
A small library to allow for sequential operations on an array using Promises
Readme
SeqProm
Type-safe promise processing for arrays in TypeScript/JavaScript
Installation
npm install seq-prom --savepnpm add seq-promOptions
| Option | Type | Description | Default | Required? |
|-----------|----------|--------------------------------------------------------------------------------------------------|---------|-----------|
| list | Array | 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
poolSizeto 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: trueandsizeto 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)
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);
}
});Using Async/Await
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));
}
});Using Resolve/Reject Functions
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();Processing Mode Examples
Pool Mode
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));Batch Mode
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.
Functions
cb
| 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
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}`);
});
});Type System
Generic Type Parameters
SeqProm is fully type-safe and supports generic type parameters for both input items and return values.
SeqProm takes two type parameters:
SeqProm<ReturnType, ItemType>({ ... })ReturnType- The type of value returned by your callback functionItemType- The type of items in your input list
Example with Complex Types
// 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();Callback Styles
Direct Return Value Style
// Simply return a value from the callback
SeqProm({
list: ['apple', 'banana', 'cherry'],
cb(item) {
// Return directly - SeqProm handles this automatically
return item.toUpperCase();
}
});Async/Await Style
// 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);
});
}Promise Chain Style
// Returning a Promise directly
SeqProm({
list: [1, 2, 3],
cb(item) {
return new Promise<string>(resolve => {
setTimeout(() => resolve(`Number: ${item}`), 100);
});
}
});Resolve/Reject Style (Traditional)
// 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}`);
}
}
});Advanced Examples with Complex Types
Working with Complex Promise Chains
// 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);
});
}Tests
npm testRelease History
- 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
