Swimmer
Swimmer is a lightweight JavaScript utility for async task pooling and throttling. It provides two main APIs: `poolAll` for inline, promise-based concurrency control, and `createPool` for more advanced, reusable pools with configurable concurrency, error handling, and lifecycle events like `onSuccess`, `onError`, and `onSettled`. The library is designed to be simple to use, ES6 and async/await ready, and has zero external dependencies, making it a 3kb addition to projects. While effective for its stated purpose, the package (version 1.4.0) has not seen active development since its last update around August 2018, and its primary author has moved on to other projects. Therefore, new features, bug fixes, or security patches are unlikely.
Common errors
-
TypeError: (0 , _swimmer.poolAll) is not a function
cause This typically occurs in CommonJS environments when `swimmer` is imported using `require()` but treated as an ES module or when bundling tools incorrectly handle the export. The library was released before widespread native ESM support in Node.js.fixEnsure you are using `import { poolAll } from 'swimmer'` in an ES module context or if using CommonJS, `const swimmer = require('swimmer'); const poolAll = swimmer.poolAll;` might be necessary, though inconsistent with documentation. Verify your build configuration for ESM/CJS transpilation. -
UnhandledPromiseRejectionWarning: A promise was rejected with a reason that was not handled
cause If a task within `poolAll` or `createPool` rejects, and you haven't attached a `.catch()` handler to the `poolAll` call or registered an `onError` callback for `createPool`, the promise rejection will go unhandled.fixFor `poolAll`, always wrap the call in a `try...catch` block: `try { await poolAll(...) } catch (err) { ... }`. For `createPool`, register an error handler: `pool.onError((err, task) => { console.error(err); });`. -
TypeError: task.then is not a function
cause This error arises when a function passed as a task to `poolAll` or `pool.add` does not return a Promise or a thenable object.fixEnsure that every function you provide to Swimmer's pooling mechanism (e.g., in `urls.map(task => () => fetch(url))`) explicitly returns a Promise. The task itself should be a function that, when called, produces a Promise.
Warnings
- breaking Swimmer is no longer actively maintained. The last release (v1.4.0) was published in August 2018, and its primary author has moved on to other projects. This means there will be no new features, bug fixes, or security updates. Users should consider this carefully for long-term projects or those requiring ongoing support.
- gotcha When using `poolAll`, any error encountered by a single task will immediately stop the entire pool and throw the error. This 'fail-fast' behavior might not be desired if you want other tasks to complete even if some fail.
- gotcha Tasks passed to `poolAll` or `pool.add` must be 'thunks' – functions that return a promise, not the promise itself. Passing an already-fired promise means Swimmer cannot manage its lifecycle.
Install
-
npm install swimmer -
yarn add swimmer -
pnpm add swimmer
Imports
- poolAll
const { poolAll } = require('swimmer')import { poolAll } from 'swimmer' - createPool
const createPool = require('swimmer').createPoolimport { createPool } from 'swimmer' - PoolInstance
const pool = createPool(...); pool.add(...)
Quickstart
import { createPool } from 'swimmer';
const urlsToProcess = [
'https://api.example.com/data/1',
'https://api.example.com/data/2',
'https://api.example.com/data/3',
'https://api.example.com/data/4',
'https://api.example.com/data/5',
'https://api.example.com/data/6',
'https://api.example.com/data/7',
'https://api.example.com/data/8'
];
// Create a new pool with a concurrency limit of 3
const dataPool = createPool({
concurrency: 3,
tasks: urlsToProcess.slice(0, 3).map(url => () => fetch(url).then(res => res.json()))
});
// Subscribe to successful task completions
dataPool.onSuccess((result, taskFn) => {
console.log(`Task successful. Result: ${JSON.stringify(result).substring(0, 50)}...`);
});
// Subscribe to errors, re-adding failed tasks for retry
dataPool.onError((err, taskFn) => {
console.error(`Task failed: ${err.message}. Re-adding to pool for retry.`);
dataPool.add(taskFn);
});
// Subscribe when the entire pool is settled (all tasks finished or retried)
dataPool.onSettled(() => {
console.log('All tasks in the pool have settled.');
});
const startProcessing = async () => {
console.log('Starting data processing with Swimmer pool...');
// Add remaining tasks to the pool
urlsToProcess.slice(3).forEach(url => {
dataPool.add(() => fetch(url).then(res => res.json()));
});
// Dynamically adjust concurrency
console.log('Increasing concurrency to 5.');
dataPool.throttle(5);
// Add a critical task and wait for its immediate completion/failure
try {
const singleResult = await dataPool.add(() => fetch('https://api.example.com/critical-data').then(res => res.json()));
console.log('Critical task completed:', JSON.stringify(singleResult).substring(0, 50), '...');
} catch (error) {
console.error('Critical task failed:', error.message);
}
// The pool will continue processing until all tasks are done or explicitly cleared.
// For demonstration, we'll let it run.
};
// Simulate API calls with a delay
const originalFetch = global.fetch;
global.fetch = async (url) => {
const delay = Math.random() * 500 + 100; // 100ms to 600ms delay
await new Promise(resolve => setTimeout(resolve, delay));
if (Math.random() < 0.1) { // 10% chance of failure
throw new Error(`Failed to fetch ${url}`);
}
return { json: () => Promise.resolve({ source: url, data: 'some_payload', timestamp: Date.now() }) };
};
startProcessing().finally(() => {
// Restore original fetch after the demonstration
global.fetch = originalFetch;
});