Skip to content

Commit

Permalink
Async Queue, Mini Rate Limitor and Fork sync (#43)
Browse files Browse the repository at this point in the history
* async task queue

* async queue and rate limiting
  • Loading branch information
amitsuthar69 authored Sep 22, 2024
1 parent 56ab723 commit e372a28
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 1 deletion.
146 changes: 146 additions & 0 deletions JavaScript/Asynchronous JS/async-queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/* Async Task Queue :
An async queue in JavaScript is a data structure designed to manage async tasks (functions that return promises) in a controlled manner.
It ensures that these tasks are processed one at a time, or in batches, while respecting their async nature.
This is useful when you need to limit the number of tasks running concurrently or when tasks must be processed in a specific order.
- Enqueue: Add an async task to the queue.
- Dequeue: Start processing a task from the queue.
- Concurrency Control: Optionally, you can control the number of tasks that run at the same time.
For example, a concurrency of 1 means tasks are processed one by one.
- Task Completion: Once a task finishes, the next task in the queue is processed.
- Error Handling: Proper handling of errors to ensure they don’t block the queue from processing the remaining tasks.
*/

// -------------------------------------------------------------------------------------------------------------------------------------------------- //

/*
steps to create a simple async task queue, processing a task one at a time :
1. create a queue and a flag to check if any task in under process.
2. create an enqueue function which will :
- accept a task and push it into the queue.
- call the processQueue function to process that pushed task.
3. processQueue function :
- check if a task is already under process, (the flag)
- if true, return immediately; else :
- set the flag to true,
- while the queue doesn't get empty :
- dequeue the task from the queue,
- execute the task (await the call to task()) and handle promise rejection error.
- set the flag to false
*/

class AsyncTaskQueue {
constructor() {
this.queue = [];
this.isProcessing = false;
}

enqueue(task) {
this.queue.push(task);
this.processQueue();
}

async processQueue() {
if (this.isProcessing) return;
this.isProcessing = true;

while (this.queue.length > 0) {
const task = this.queue.shift();
try {
await task(); // here awaiting the task is important as we wait untill it's done i.e blocking the execution of other tasks
} catch (error) {
console.log("Error in processing task", error);
}
}
this.isProcessing = false;
}
}

const asyncQueue = new AsyncTaskQueue();

const task = async (id) => {
console.log(`Task ${id} queued`);
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
const data = await response.json();
console.log({ id: data.id, title: data.title });
console.log(`Task ${id} ended`);
};

for (let i = 1; i <= 10; i++) {
asyncQueue.enqueue(() => task(i));
}

/*
Here is a version of the async queue which is quite efficient and introduces the concept of concurrency control
by limiting the number of "workers" (i.e., concurrent tasks) that can be processed at any given time.
*/

class RateLimitedQueue {
constructor(maxRequests) {
this.maxRequests = maxRequests;
this.queue = [];
this.activeTasks = 0;
}

enqueue(task) {
this.queue.push(task);
this.processQueue();
}

async processQueue() {
if (this.activeTasks >= this.maxRequests) return; // Ensuring rate limit is respected

while (this.activeTasks < this.maxRequests && this.queue.length > 0) {
const task = this.queue.shift(); // Get the next task in the queue
this.activeTasks++;

// Process the task
task()
.then((result) => {
console.log(result); // process the result
})
.catch((error) => {
console.error("Task failed:", error);
})
.finally(() => {
this.activeTasks--; // Mark task as completed
this.processQueue(); // Continue processing the queue
});
}

// Wait 1 second before processing the next batch of requests
if (this.queue.length > 0) {
setTimeout(() => {
this.processQueue();
}, 1000);
// the 1000 ensures that no more than maxRequests tasks are processed every second.
}
}
}

/*
The hardcoded wait (setTimeout) of 1000 is there to simulate rate limiting
and ensure you don’t overwhelm a system that has constraints (e.g., API rate limits).
If you don’t need rate limiting, you can remove the wait
and let the tasks be processed as fast as possible, up to your concurrency limit (maxNumOfWorkers).
*/

const makeApiRequest = (id) => {
return () =>
fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
.then((response) => response.json())
.then((data) => {
// console.log(`Request ${id} complete`, data);
return { id: data.id, title: data.title };
});
};

const rateLimitedQueue = new RateLimitedQueue(3); // Limit to 3 API requests per second

// enqueuing 20 tasks to fetch data from the API
for (let i = 10; i <= 20; i++) {
rateLimitedQueue.enqueue(makeApiRequest(i));
}
41 changes: 40 additions & 1 deletion JavaScript/Asynchronous JS/then-catch.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ p1.then(
function (error) {
/* handle an error */
console.log(error);
}, // doesn't run if there's no error
} // doesn't run if there's no error
);

/* If we’re interested only in successful completions,
Expand Down Expand Up @@ -51,4 +51,43 @@ f2().catch((err) => {
// catching the error
console.log(err);
});

// ----------------------------------------------------------------------- //

const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("resolved!");
}, 2000);
});

p1.then(
(value) => console.log(value), // onResolve
(error) => console.log(error) // onReject
);

Promise.resolve("foo")
// 1. Receive "foo", concatenate "bar" to it, and resolve that to the next then
.then(
(string) =>
new Promise((resolve, reject) => {
setTimeout(() => {
string += "bar";
resolve(string);
}, 1);
})
)
// 2. receive "foobar", register a callback function to work on that string
// and print it to the console, but not before returning the unworked on
// string to the next then
.then((string) => {
setTimeout(() => {
string += "baz";
console.log(string); // foobarbaz
}, 1);
return string;
});

// Logs, in order:
// foobar
// foobarbaz
```

0 comments on commit e372a28

Please sign in to comment.