10 Javascript tools for clear code design


Hey there, developer! You might need some tools to boost your skills, and here are a few to check out: (https://jsr.io/@panth977/tools)
Rate limit function invocation
Sometimes you just want to make sure you don't go over the max number of calls to a service. To keep things in check and avoid hitting the rate limit, try using TOOLS.CreateParallelTaskManager
.
const serviceToSq = TOOLS.CreateParallelTaskManager({
maxParallelExecution: 2,
async implementation(x) {
console.log('start', x);
await TOOLS.delay(x * 1000);
console.log('end', x);
return x ** 2;
},
});
serviceToSq(5);
serviceToSq(2);
serviceToSq(4);
serviceToSq(10);
serviceToSq(9);
/** CONSOLE **/
(00 sec) start 5
(00 sec) start 2
(02 sec) end 2
(02 sec) start 4
(05 sec) end 5
(05 sec) start 10
(06 sec) end 4
(06 sec) start 9
(15 sec) end 10
(15 sec) end 9
Batch multiple Invocations
Maybe you've got a speedy API call for the backend that handles multiple operations in one go, but you don't want to complicate your development process. In that case, try using TOOLS.CreateBatchProcessor
.
const RedisMethods = {
get: TOOLS.CreateBatchProcessor<string, string | null>({
delayInMs: 0,
async implementation(args) {
console.log('<0>', args);
return await redis.mget(args);
},
}),
getAfter10ms: TOOLS.CreateBatchProcessor<string, string | null>({
delayInMs: 10,
async implementation(args) {
console.log('<10>', args);
return await redis.mget(args);
},
}),
}
RedisMethods.get('key1');
RedisMethods.get('key2');
delay(12).then(() => RedisMethods.get('key3'));
delay(12).then(() => RedisMethods.get('key4'));
RedisMethods.getAfter10ms('key5');
RedisMethods.getAfter10ms('key6');
delay(5).then(() => RedisMethods.getAfter10ms('key7'));
delay(12).then(() => RedisMethods.getAfter10ms('key8'));
/** CONSOLE **/
(00 ms) <0> ['key1', 'key2']
(10 ms) <0> ['key3', 'key4']
(10 ms) <10> ['key5', 'key6', 'key7']
(22 ms) <10> ['key8']
Internal PubSub
We've all seen code where we pile on tons of features after creating a user. Sure, you could set up a PubSub, but that takes time. Unless you're handling something big, you can just do it on the API server. No need to set up a separate background worker service and build a whole PubSub messaging system. Try using TOOLS.CreatePubsub
to keep your code nice and clear.
const userChanges = TOOLS.CreatePubsub(z.object({
event: z.enum(['CREATE', 'UPDATE', 'DELETE']),
data: z.object({
id: z.number().int(),
email: z.string().email(),
name: z.string().max(225),
...
}),
}));
function createUser(userData) {
const rows = pg.query(`INSERT INTO users VALUES (...) RETURNING *`);
await userChanges.publish({ event: 'CREATE', data: rows[0] });
}
userChanges.subscribe(function ({ event, data }) {
if (event === 'CREATE') {
// send welcome email
} else if (event === 'DELETE') {
// send good bye email
}
});
userChanges.subscribe(function () {
if (event === 'CREATE') {
// setup records
} else if (event === 'DELETE') {
// archive records
}
});
Data Structures One To One Mapping
You often deal with databases that return rows, and you probably want a Dictionary structure to make life easier. We all know how annoying it is to use .find
on rows every single time. Give TOOLS.oneToOneMapping
a shot. We also have TOOLS.oneToManyMapping
and TOOLS.oneToOneToOneMapping
.
function getUsers(userIds) {
const rows = pg.query(`SELECT * FROM users WHERE id IN (...)`)
const users = TOOLS.oneToOneMapping({ rows, keyPath: "id" });
return users;
}
const users = getUsers([userId1, userId2]);
const user1 = users[userId1];
const user2 = users[userId2];
Data Structures One To Many Mapping
function getUsersFriends(userIds) {
const rows = pg.query(`
SELECT f.*
FROM users u
JOIN friends f ON f.userId = u.id
WHERE u.id IN (...)
`)
const friends = TOOLS.oneToManyMapping({ rows, keyPath: "id" });
return friends;
}
const friends = getUsersFriends([userId1, userId2]);
const friendsOfUser1 = friends[userId1];
const friendsOfUser2 = friends[userId2];
Data Structures One To One To Many Mapping
I honestly doubt you'll ever need this monster! But hey, I've got it here just in case. (NOTE: If you find yourself needing this in more than one place, you really should rethink your code architecture.)
Encode Connection String
Have you checked out env.PG_HOST
, env.PG_PORT
, env.PG_DB
, env.PG_USER
, env.PG_PASS
? What if we could simplify all these different environment variables into a single env.PG_CONNECTION_STRING
? That would be awesome! Here are the TOOLS.encodeConnectionString
encoder and TOOLS.decodeConnectionString
decoder for that.
MY_CONNECTION_STRING: TOOLS.encodeConnectionString({
protocol: 'myprotocol',
user: 'myuser',
pass: 'mypass',
host: 'myhost',
port: 1234, // optional
db: 'mydb', // optional
params: { //optional
log: 'someval',
ssl: 'true',
max: '20',
},
})
// myprotocol://myuser:mypass@myhost:1234/mydb?log=someval&ssl=true&max=20
MY_CONNECTION_OPTIONS: TOOLS.decodeConnectionString(MY_CONNECTION_STRING)
No worries, user
, pass
, and host
are all encoded with encodeURIComponent
and decoded with decodeURIComponent
. Plus, params
are handled with URLSearchParams
, making life easier for everyone!
Sort
A quick sort function for an array to sort based on some deeply nested field, just the way you need it.
const myArray: {
id: number;
age: number; // int values only
stats: {
score: number;
goals: number;
...
};
...
}[] = [...];
const sortedOnScore = TOOLS.sortList({
rows: myArray,
mode: 'ASC',
keyPath: 'stats.score',
});
const sortedOnAge = TOOLS.sortList({
rows: myArray,
mode: 'ASC',
keyPath: 'age',
algorithm: 'bucket',
});
Dynamic destructuring
You might need to grab a bunch of data from a SQL select query and turn it into a JSON for the frontend. Here's how you can do it easily using TOOLS.destructure
const rows = await pg.query(`
SELECT
v.id "videoId",
c.email "creator.email",
c.name "creator.name",
c.handle "creator.handle",
v.url "video.url",
v.share_id "video.share_id",
v.description "meta.description"
...
FROM ...
JOIN ...
WHERE ...
`);
const videosJson = TOOLS.destructure({ rows });
type Video = {
videoId: number;
creator: {
email: string;
name: string;
handle: string;
};
video: {
url: string;
share_id: string;
description: string;
};
...
}
return videosJson as Video[];
All Promise Stats
Let's say you're running a bunch of promises at the same time, and this is for a worker API. You probably want to track some stats to see which jobs were successful and which ones weren't. Here's how you can do it using TOOLS.SettleAllPromise
const jobs: Record<string, Promise<any>> = {};
for (const cluster of clusters) {
jobs[cluster.id] = runCluster(cluster);
}
const stats = TOOLS.SettleAllPromise(jobs);
stats.errors; // = Record<string, errors>
stats.results; // = Record<string, errors>
stats.stats; // {total, status, error?, errorRate?}
Delay
You've got a super long sync process happening, and if you didn't know, JavaScript runs on a single thread. Unless you're using workers, you're stuck with just one core, and none of your incoming requests will get processed because that one thread is busy with a sync task.
If you're familiar with the JavaScript event loop, you know there's a way out. Just figure out your checkpoints where you can let the main thread handle other tasks and then come back to finish the rest of the process. TOOLS.delay
comes to rescue.
function heavyTask1() {
//
}
function heavyTask2() {
//
}
function heavyTask3() {
//
}
function typicalImplementation() {
heavyTask1();
heavyTask2();
heavyTask3();
}
async function betterImplementation() {
heavyTask1();
// complete all the other task from task que & event que
await TOOLS.delay();
heavyTask2();
// complete all the other task from task que & event que
// if not already 250ms completed, wait of it to complete!
await TOOLS.delay(250);
heavyTask3();
}
Check out https://jsr.io/@panth977 to make your project clearly coded instead of that clean coding nonsense...
Subscribe to my newsletter
Read articles from Panth Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
