for the record (and disregarding how appropriate this is as an interview question): in JS you can (ab)use the event loop and promise chains to do this for you without managing any queues or lists manually. You have a single `let job = Promise.success();` as a global var, and scheduling a new job becomes `job = job.then(f, errHandler).then(callback, errHandler)`. It's a nightmare to debug (because you can't "see" the in-process queue) but it means you don't have to muck around with manual lists, queues, loops, shift/unshift, "isProcessing" flags etc, all of which is basically you reimplementing that native functionality in user space. It completely sidesteps the bug of TFAs naive implementation.
Not advocating for this in prod but in the context of a programming puzzle it can be neat.
late edit: ironically this is also a comment on the LLM talk in TFA: messing with the event loop like this can give you a strong mental model of JS semantics. Using LLMs I would just have accepted a loop and never learned about promise chains. This is the risk in using LLMs: you plateau. If you will allow a tortured metaphor: my naive understanding of SR is that you always move at light speed, but in 4 dimensions, so the faster you move in the 3D world, the slower you move through time, and vice versa. Skill is similar: your skill vector is always a fixed size (= "talent"?). If you use LLMs, it's basically flat: complete tasks fast but learn nothing. Without them, you move diagonally upwards: always improving, but slower in the "task completion" plane. Are you ready to plateau?
If you don't care about the order of requests then you can just set up a flag to denote if a task is running, and keep rescheduling the other tasks. Something like
let isProcessing = false;
async function checkFlagAndRun(task) {
if (isProcessing) {
return setTimeout(() => checkFlagAndRun(task), 0);
}
isProcessing = true;
await task();
isProcessing = false;
}
should do the trick. You can test it with
function delayedLog(message, delay) {
return new Promise(resolve => {
setTimeout(() => {
console.log(message);
resolve();
}, delay);
});
}
function test(name,num) {
for (let i = 1; i <= num; i++) {
const delay = Math.floor(Math.random() * 1000 + 1);
checkFlagAndRun(() => delayedLog(`${name}-${i} waited ${delay} ms`, delay));
}
}
test('t1',20); test('t2',20); test('t3',20);
BTW, for 4 scheduled tasks, it basically always keeps the order, and I am not sure why. Even if the first task always runs first, the rest 3 should race each other. 5 simultaneously scheduled tasks ruins the order.
I tried this at work but ran into issues where if the server had some error or issue or took longer than expected, the job queue grew too large and caused OOM issues. I had to turn it into a manual list in order to debug the problem, though.
Plus we have a case where a certain type of request should skip to the front of the queue.
Leaning on promises does cut out a lot of the user space complication though.
I expected this to be the answer. I guess the interview is not necessarily for JavaScript programmers, but this seems like the correct solution. It brings in some facilities for dealing with errors, too.
Not advocating for this in prod but in the context of a programming puzzle it can be neat.
late edit: ironically this is also a comment on the LLM talk in TFA: messing with the event loop like this can give you a strong mental model of JS semantics. Using LLMs I would just have accepted a loop and never learned about promise chains. This is the risk in using LLMs: you plateau. If you will allow a tortured metaphor: my naive understanding of SR is that you always move at light speed, but in 4 dimensions, so the faster you move in the 3D world, the slower you move through time, and vice versa. Skill is similar: your skill vector is always a fixed size (= "talent"?). If you use LLMs, it's basically flat: complete tasks fast but learn nothing. Without them, you move diagonally upwards: always improving, but slower in the "task completion" plane. Are you ready to plateau?