I am trying to implement two classes that can deal with asynchronous tasks in JavaScript:
Class Task: mimics the execution of a task with setTimeout. Once the timer expires, the task is considered completed.
Class TaskManager: has a capacity parameter to limit the numbers of tasks that can be executing in parallel.
I thought if I could just call the loop function recursively, just to keep checking if one job is done, I could proceed to the next job. But this leads immediately to a "Maximum call stack size exceeded" error.
Can someone explain how I can fix this?
class Task {
constructor(time) {
this.time = time;
this.running = 0;
}
run(limit, jobs, index) {
setTimeout(() => {
console.log('hello', index);
this.done(limit, jobs, index);
}, this.time);
}
done(limit, jobs, index) {
jobs.splice(index, 1);
console.log(jobs);
}
}
class TaskManager {
constructor(capacity) {
this.capacity = capacity;
this.jobs = [];
this.index = 0;
this.running = 0;
this.pending = [];
}
push(tk) {
this.jobs.push(tk);
this.index += 1;
const loop = () => {
if (this.jobs.length === 0) {
return;
}
if (this.jobs.length <= this.capacity) {
this.running += 1;
tk.run(this.capacity, this.jobs, this.index-1);
return;
}
loop();
}
loop();
}
}
const task = new Task(100);
const task1 = new Task(200);
const task2 = new Task(400);
const task3 = new Task(5000);
const task4 = new Task(6000);
const manager = new TaskManager(3);
manager.push(task);
manager.push(task1);
manager.push(task2);
manager.push(task3);
manager.push(task4);
You should not implement the busy loop, as that will block the event loop and so no user UI events or setTimeout events will be processed.
Instead respond to asynchronous events.
If you let the setTimeout callback resolve a Promise, it is not so hard to do.
I modified your script quite drastically. Here is the result:
class Task {
constructor(id, time) {
this.id = id;
this.time = time;
}
run() {
console.log(this + ' launched.');
return new Promise(resolve => {
setTimeout(() => {
console.log(this + ' completed.');
resolve();
}, this.time);
});
}
toString() {
return `Task ${this.id}[${this.time}ms]`;
}
}
class TaskManager {
constructor(capacity) {
this.capacity = capacity;
this.waiting = [];
this.running = [];
}
push(tk) {
this.waiting.push(tk);
if (this.running.length < this.capacity) {
this.next();
} else {
console.log(tk + ' put on hold.');
}
}
next() {
const task = this.waiting.shift();
if (!task) {
if (!this.running.length) {
console.log("All done.");
}
return; // No new tasks
}
this.running.push(task);
const runningTask = task.run();
console.log("Currently running: " + this.running);
runningTask.then(() => {
this.running = this.running.filter(t => t !== task);
console.log("Currently running: " + this.running);
this.next();
});
}
}
const a = new Task('A', 100);
const b = new Task('B', 200);
const c = new Task('C', 400);
const d = new Task('D', 5000);
const e = new Task('E', 6000);
const manager = new TaskManager(3);
manager.push(a);
manager.push(b);
manager.push(c);
manager.push(d);
manager.push(e);
Related
Question as below:
Create a function GoodMan
GoodMan("Tom")
output: I am Tom
GoodMan("Tom").rest(10).learn("computer")
output: I am Tom
//wait 10 seconds
Start learning after 10 seconds
Learning computer
GoodMan("Tom").restFirst(5).learn("english")
output:
//wait 5 seconds
Start learning after 5 seconds
I am Tom
Learning english
I have finished the first two questions. However, in the third question, how to execute the restFirst(5) function firstly in chain functions in Javascript?
Should I add a selector to determine whether GoodMan("Tom") execute.
Code shows below:
var GoodMan = function(str){
var callback_f = new Object;
str1 = "I am " + str;
console.log(str1);
callback_f.rest = function(num){
sleep(num * 1000);
console.log ("Start learning after " + num + " seconds");
return callback_f;
};
callback_f.learn = function(str){
str2 = "Leaning " + str;
console.log (str2);
};
return callback_f;
}
function sleep(ms) {
var unixtime_ms = new Date().getTime();
while(new Date().getTime() < unixtime_ms + ms) {}
}
GoodMan("Tom").rest(10).learn("computer");
How about having a queue of tasks?
function TaskQueue(tasks) {
return function createQueue(...args) {
const queue = [];
let running = false;
async function processQueue() {
if(running) return;
running = true;
// console.log(queue);
for(const { task, args } of queue) await task(...args);
running = false;
}
function addTask(task, priority, args) {
queue[priority ? "unshift" : "push"]({ task, args });
setTimeout(processQueue);
}
const instance = {};
for(const [key, priority, task] of tasks) {
instance[key] = function(...args) { addTask(task, priority, args); return this; };
}
if(instance.constructor) instance.constructor(...args);
return instance;
};
}
const GoodMan = TaskQueue([
["constructor", 0, (name) => console.log(`Hi, ${name}`)],
["rest", 0, (time) => new Promise(res => setTimeout(res, time * 1000))],
["restFirst", 1, (time) => new Promise(res => setTimeout(res, time * 1000))],
["learn", 0, subject => console.log(`Learning ${subject}`)]
]);
GoodMan("Tom").restFirst(5).learn("english")
You can try the code below:
const goodMan = (name) => {
return Promise.resolve(`I am ${name}`);
};
const restFirst = (time) => {
return Promise.resolve(`Start learning after ${time} seconds`);
};
const learn = (subject) => {
return Promise.resolve(`Learning ${subject}`);
};
const allTogether = (async () => {
const goodManOutput = await goodMan('Tom');
const restOutput = await restFirst(5);
const learnOutput = await learn('English');
return Promise.resolve(`${restOutput} ${goodManOutput} ${learnOutput}`);
});
allTogether().then(newOutput => {
console.log(newOutput);
});
Hope this helps.
Here is what should be output to console:
1 hello
2 a
3 b
And here is the code for which i should make a class or a function:
var d = new deferred();
d.then(function(res) {
console.log("1 ", res);
var d1 = new deferred();
setTimeout(function() {
d1.resolve("a");
}, 150);
return d1;
});
d.then(function(res) {
console.log("2 ", res);
return "b";
});
d.then(function(res) {
console.log("3 ", res);
return "c";
});
d.resolve("hello");
I should create a class or a
function with name "deferred"
I've already done almost everything except i can't make it get the result from setTimeout
function.
function deferred() {
this.arrfunc = [];
this.buffstr = null;
this.bufffunc;
this.result;
this.then = function(callback) {
this.arrfunc.push(callback);
}
this.wait = function() {
while (this.buffstr == null) {}
return this.buffstr;
}
this.resolve = function(str) {
this.buffstr = str;
while (this.arrfunc.length != 0) {
//console.log(typeof(this.buffstr));
if (typeof(this.buffstr) === "object") {
this.buffstr = this.buffstr.wait();
}
this.bufffunc = this.arrfunc.shift();
this.buffstr = this.bufffunc(this.buffstr);
}
}
}
The main problem in my implemenetation that its somehow stuck in while loop. And don't want to get a result after setTimeout expired.
It seems like maybe you are making this too complicated with all the different state properties. If these are you only requirements, you really only need a queue of callbacks to hold on to all the functions passed into then(). Like regular promises you need to act differently depending on whether the callback returns a regular value or another deferred instance.
function deferred() {
this.callbacks = []
}
deferred.prototype.then = function(cb) {
this.callbacks.push(cb)
}
deferred.prototype.resolve = function(val) {
let f = this.callbacks.shift()
if (f) {
let n = f(val)
if (n instanceof deferred) n.then((v) => this.resolve(v))
else this.resolve(n)
}
}
var d = new deferred("one");
d.then(function(res) {
console.log("1 ", res);
var d1 = new deferred("two");
setTimeout(function() {
d1.resolve("a");
}, 550);
return d1;
});
d.then(function(res) {
console.log("2 ", res);
return "b";
});
d.then(function(res) {
console.log("3 ", res);
return "c";
});
d.resolve("hello")
In the following piece of code I try to demonstrate how concatMap preserves the order of events, even if the action performed on events do not complete in order.
Now I get the error that
delayerObservable.complete() is not a function
This basically is just taken from a tutorial. I call next(), and then I call complete(). It should work, at least that's what I thought.
I can achive the desired functionality by returning randomDelayer.first()
return randomDelayer.first()
but I would like to complete the observable from within, since I might want to send out more events than just one.
const myTimer = Rx.Observable.create((observer) => {
let counter = 0;
setInterval( () => {
observer.next(counter++);
console.log('called next with counter: '+ counter);
},2000);
});
const myRandomDelayer = myTimer.concatMap( (value) => {
const randomDelayer = Rx.Observable.create( (delayerObservable) => {
const delay = Math.floor(Math.random()*1000);
setTimeout(() => {
console.log(delayerObservable);
delayerObservable.next('Hello, I am Number ' + value + ' and this was my delay: ' + delay);
delayerObservable.complete(); // <<-- this does not work (not a function)
}, delay);
});
return randomDelayer;
});
myRandomDelayer.subscribe( (message) => {
console.log(message);
});
It seems that there are quite a few changes between version 4 and 6 of the rxjs-framework. The working version of the defective source is this:
const { Observable } = rxjs;
const { map, filter, concatMap, pipe } = rxjs.operators;
console.log('Starting....');
const myTimer = Observable.create((observer) => {
let counter = 0;
setInterval( () => {
counter++;
if (counter < 10){
console.log('nexting now with counter ' + counter);
observer.next(counter);
} else {
observer.complete();
}
},1000);
});
const myRandomDelayer = myTimer.pipe(
concatMap( (value) => {
const randomDelayer = Observable.create( (delayerObservable) => {
const delay = Math.floor(Math.random()*5000);
setTimeout(() => {
delayerObservable.next('Hello, I am Number ' + value + ' and this was my delay: ' + delay);
delayerObservable.complete();
}, delay);
});
return randomDelayer;
})
);
myRandomDelayer.subscribe( (message) => {
console.log(message);
});
I have an async function that has a loop that I need to be able to pause or unpause it. This is what I have so far.
I use a flag to pause the flow:
let flag = true;
function flag_func() {
flag = !flag;
}
$(document).ready(function () {
function sleep(ms) {
while (!flag) {
//...waiting.. but infinite loop
}
return new Promise(resolve => setTimeout(resolve, ms));
}
async function show_simulation(data) {
document.getElementById("solve-button").outerHTML = "<button type=\"button\" id='pause-button' onclick='flag_func()' class=\"btn btn-primary btn-lg\">Pause</button>";
//simulation
if (data.length === 0) {
console.log('stuff')
} else {
let i;
for (i = 0; i < data.length; i++) {
await sleep(40);
// do stuff
}
}
}
});
The problem is that is being paused, but due the while block the flow, I can't unpause the for loop.
Any idea about how I can solve this?
It might be a nice use case for async iterables. It involves a bit of boilerplate to create your async list, but then the code is much nicer. Basically you would have:
import AsyncList from './async-list.js'
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function f(data) {
const list = new AsyncList(data);
document.getElementById("btn-toggle").addEventListener("click", function () {
if (list.paused) {
this.textContent = "Pause";
list.resume();
} else {
this.textContent = "Resume";
list.pause()
}
})
for await (let item of list) {
console.log(item)
await sleep(1000);
}
console.log("end of loop")
}
f([10, "hello", 1029, 90, 80, 209, 44])
A possible implementation of AsyncList could be:
export default class AsyncList {
constructor(array) {
// shallow copy
this._array = array.slice();
this._index = 0;
this._length = this._array.length;
this.paused = false;
this._resume = () => {}; // noop, in case `resume` is called before `pause`
}
[Symbol.asyncIterator]() {
return this;
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
this._resume();
}
next() {
if (this._index < this._length) {
const value = this._array[this._index++];
if (this.paused) {
return new Promise(r => this._resume = r.bind(null, { value }))
}
return Promise.resolve({ value })
} else {
return Promise.resolve({ done: true });
}
}
}
Just to give to you the idea, you could also encapsulate the private properties, and check more scenarios (here I assume data is an array, for example, not just an iterable).
I'd replace:
let i;
for (i = 0; i < data.length; i++) {
await sleep(40);
// do stuff
}
...with...
let i = 0;
const doStuff = () => {
// do stuff
if (++i < data.length) {
setTimeout(doStuff, 40);
}
};
setTimeout(doStuff, 40);
I have a simple class below that starts and then updates a count every second. How would I go about adding functionality for it to listen for a specific value and then fire a callback?
function Counter() {
this.currentCount = 0;
}
Counter.prototype.start = function() {
setInterval(this.update, 1000);
};
Counter.prototype.when = function(value, callback) {
callback(value);
};
Counter.prototype.update = function() {
this.currentCount++;
};
In my mind it would work something like this.
var counter = new Counter();
counter.when(50, function(value) {
console.log('We arrived at ' + value + ', the requested value.');
});
counter.start();
This is just a nice homework, I'll do that for you ;) Please have a look on my solution:
function Counter() {
this.currentCount = 0;
this.conditions = [];
this.interval = undefined;
}
Counter.prototype.start = function() {
if (!this.interval) {
var that = this;
this.interval = setInterval(function () {
that.update();
}, 1000);
}
};
Counter.prototype.stop = function () {
if (this.interval) {
clearInterval(this.interval);
this.interval = undefined;
}
this.currentCount = 0;
};
Counter.prototype.when = function(value, callback) {
var that = this;
this.conditions.push(function () {
if (that.currentCount === value) {
callback.call(that, value);
}
});
};
Counter.prototype.update = function() {
this.currentCount++;
for (var i = 0, l = this.conditions.length; i < l; i++) {
var condition = this.conditions[i];
condition();
}
};
var counter = new Counter();
counter.when(50, function(value) {
console.log('We arrived at ' + value + ', the requested value.');
});
counter.when(60, function (value) {
console.log('Stop at ' + value + '!');
this.stop();
});
counter.start();
and it's fiddled!
Another answer here made a good argument in hiding private variables, but implemented it a bit too confused, so this is another way of doing it similar. Instead of shared prototype functions this is using instance functions. Some may say this needs more memory, but I don't believe it's significant, and allows to easily have privates in a real constructor function.
var Counter = function () {
var that = this, currentCount = 0,
conditions = [], interval;
var update = function () {
currentCount++;
for (var i = 0, l = conditions.length; i < l; i++) {
var condition = conditions[i];
condition();
}
};
this.start = function () {
if (!interval) {
interval = setInterval(function() {
update.call(that);
}, 1000);
}
};
this.when = function (value, callback) {
conditions.push(function () {
if (currentCount === value) {
callback.call(that, value);
}
});
};
this.stop = function () {
if (interval) {
clearInterval(interval);
interval = undefined;
}
currentCount = 0;
};
};
var counter = new Counter();
counter.when(50, function(value) {
console.log('We arrived at ' + value + ', the requested value.');
});
counter.when(60, function (value) {
console.log('Stop at ' + value + '!');
this.stop();
});
counter.start();
see it fiddled!
Notice also that in both examples, counter is instanceof Counter and Object,
whereas Counter is instanceof Function and Object (why I like to write so much code ;))
To support multiple whens:
Add an array of whens in your class:
function Counter() {
this.currentCount = 0;
this.whens = [];
}
Then let the when function push to that:
Counter.prototype.when = function(value, callback) {
this.whens.push({'time' : value, 'callback' : callback});
}
And check for these whens at update:
Counter.prototype.update = function() {
this.currentCount++;
for(var w in this.whens) {
if(this.currentCount == this.whens[w].time) {
this.whens[w].callback();
}
}
}
Try something more like:
function Counter(interval, val, func){
this.currentCount = 0;
setInterval(function(){
this.currentCount++;
if(this.currentCount === val)func();
}, interval);
}
var nc = new Counter(1000, 50, function(){
console.log('We have arrived at '+nc.currrentCount);
});
There is an argument to be made for something like this instead:
var Counter = (function() {
var update = function() {
var idx, callbacks;
this.currentCount++;
callbacks = this.callbacks[this.currentCount];
if (callbacks) {
for (idx = 0; idx < callbacks.length; idx++) {
callbacks[idx](this.currentCount);
}
}
};
var start = function() {
var counter = this;
setInterval(function() {update.call(counter)}, 1000);
};
var when = function(count, callback) {
(this.callbacks[count] || (this.callbacks[count] = [])).push(callback);
};
return function() {
var config = {currentCount: 0, callbacks: {}};
this.start = function() {return start.call(config);};
this.when = function(count, callback) {
return when.call(config, count, callback);
};
// this.stop = ... // if desired
};
}());
This is somewhat more memory intensive than the prototype-based version of the code. I wouldn't use it for something where you were expecting hundreds of thousands or millions of objects. But it has the advantage that it truly encapsulates the data you might like to keep hidden, such as currentCount and the list of callbacks. It doesn't expose an unnecessary update function. And it's not terribly more heavy than the prototype version. Each instance has its own start and when functions, but those are just thin wrappers around common functions.
It is a bit more difficult to add a stop function to this in the same manner, unless you don't mind exposing the the result of setInterval. But it is doable.