6 ms·
A job queue in two lines of JavaScript
- lerp-io 1y agoThe chain variable continuously grows because each call to enqueue() creates a new promise that references the previous one. This creates an ever-growing chain of promises that never gets garbage collected.
- pinoy420 1y ago[dead]
- ath92 1y agoCouldn’t the browser garbage collect the promises (and their callbacks) that have been rejected or resolved? I.e. effectively “shift” the promises at the start of the queue that have been dealt with and will never be relevant again? At least this stackoverflow answer suggests the handlers are GC’ed: https://stackoverflow.com/questions/79448475/are-promise-handlers-cleared-from-memory-after-the-promise-is-resolved https://stackoverflow.com/questions/79448475/are-promise-han...
- jameshart 1y agoWhy does the promise returned by a promise’s then() method need to reference that promise? The original promise needs to reference the chained promise, not the other way round. As jobs complete I would expect the top of the chain to be eligible to be garbage collected.
- paulddraper 1y agoThough it seems like that, no. a = b.then(c); When `b` is resolved, it is garbage collected, even if a reference to `a` is retained. (If the runtime maintains async stack traces, that could be an issue...but that is a common issue, and the stack depth is limited.)
- deleted 1y ago[deleted]
- bapak 1y agoThat's not how promises work, there's no chain. It's no different from await job1() await job2() await job3() await job4() Except dynamic
- ameliaquining 1y agoAt least in V8, this is not the case. To prove it, start a Chromium-based browser with the command-line flag --js-flags=--expose-gc, then go to https://runjs.app/play/#aWYgKCF3aW5kb3cuZ2MpIHsKICB0aHJvdyBuZXcgRXJyb3IoJ1RoaXMgb25seSB3b3JrcyBpbiBDaHJvbWl1bSB3aXRoIC0tanMtZmxhZ3M9LS1leHBvc2UtZ2MnKTsKfQpsZXQgZmlyc3QgPSBQcm9taXNlLnJlc29sdmUoKTsKY29uc3QgcmVmID0gbmV3IFdlYWtSZWYoZmlyc3QpOwpjb25zdCBzZWNvbmQgPSBmaXJzdC50aGVuKCk7CmZpcnN0ID0gdW5kZWZpbmVkOwphd2FpdCBnYyh7dHlwZTogJ21ham9yJywgZXhlY3V0aW9uOiAnYXN5bmMnfSk7CmNvbnNvbGUubG9nKGBGaXJzdCBwcm9taXNlICR7cmVmLmRlcmVmKCkgPyAnaXMgbm90JyA6ICdpcyd9IGdhcmJhZ2UgY29sbGVjdGVkYCk7CnZvaWQgc2Vjb25kLnRoZW47ICAvLyBEb24ndCBsZXQgc2Vjb25kIGJlIGdhcmJhZ2UgY29sbGVjdGVkIHVudGlsIG5vdwo= https://runjs.app/play/#aWYgKCF3aW5kb3cuZ2MpIHsKICB0aHJvdyBu...
- bigiain 1y ago> This creates an ever-growing chain of promises that never gets garbage collected. Modern frontend development performance best practise is to allow for garbage collection only when the browser crashes.
- moralestapia 1y agoNope.
- Inviz 1y agoI wonder if people who are claiming its not the case ever tried to use promises in hardcore ways like this, chaining 10s of thousands of calls. You often hit some bullshit, i.e. Promise.race is a big cause of issues. Even if you are doing `then().then()`, something _else_ in the code base could retain the promise somehow and then your whole chain isn't GCable! For example https://github.com/rxaviers/async-pool https://github.com/rxaviers/async-pool library that implements concurrency for async iterators (and uses promise.race) subtly creates GC pressure which you dont see until you make a whole lot of calls and suddenly its slow
- gabrielsroka 1y agoThat's TS, not JS.
- 90s_dev 1y agoFor now. https://github.com/tc39/proposal-type-annotations https://github.com/tc39/proposal-type-annotations
- jameshart 1y agoOne line of TS, then two lines of JS (with a type annotation in one of them)
- goloroden 1y agoIt’s not even 2 lines: 1. Type definition 2. Chain definition 3. Enqueue definition
- jakelazaroff 1y agoLine 1 is fully erased when transpiling, leaving two lines of JavaScript.
- Brian_K_White 1y agoBut you still had to write it and it's required to get the result, so I don't see any rational for not counting it. Transpilation does not change the fact that the input is these 3 lines of code in 2 different languages.
- jakelazaroff 1y agoRight, and only two of those lines are JavaScript. The type definition is not required to get the result.
- metadat 1y agoI noticed the same thing when I saw the code. Is it honest for TFA to title it as "2-lines of Javascript" in this case?
- 90s_dev 1y agoI came across this trick a few months ago when I was dealing with what seemed to be a race condition from a chrome-only API, and I just felt so clever! I don't remember though whether the race condition actually was one or not though, and I eventually had to rip that entire class out of my codebase. But it's such a good feeling to know I came up with a solution that clever all by myself.
- Inviz 1y agoChaining promises like this is not a good idea for high throughput cases (creates gc pressure), but perfectly valid for regular cases.
- paulddraper 1y agoHuh? It creates 1 object allocation per enqueue.
- Inviz 1y agoyeah but it creates closure as well, which typically references some objects that isn't easy to GC. So if you have long-living promise with some data captured in closure, it will not be cleared. So doing then().then().then() may not release objects referenced in callbacks that resolved time ago.
- paulddraper 1y agoThere are zero closures here.
- vivzkestrel 1y agoA production grade application would need a much more robust mechanism like BullMQ
- ameliaquining 1y agoThere are use cases for a non-persistent task queue, like doing things one at a time on the frontend. (JavaScript is single-threaded but will interleave multiple separate async call chains that each span multiple event-loop iterations, and there could be situations where you might not want that.)
- theThree 1y agoSomething like this? async function runTasks(tasks: Job[]) { for (let task of tasks) { try { await task() } catch (e) { } } }
- foxygen 1y agoThis only works if you have the full list of tasks beforehand.
- neals 1y agoAnybody willing to walk me through this code? I don't get it.
- foxygen 1y agochain is a global variable. It starts with an empty promise. First call to enqueue changes the (global)value of chain to emptyPromise.then(firstJob), second call to enqueue changes it to emptyPromise.then(firstJob).then(secondJob) and so on.
- fwlr 1y agoJavaScript promises are objects with a resolver function and an internal asynchronous computation. At some point in the future, the asynchronous computation will complete, and at that point the promise will call its resolver function with the return value of the computation. `prom.then(fn)` creates a new promise. The new promise’s resolver function is the `fn` inside `then(fn)`, and the new promise’s asynchronous computation is the original promise’s resolver function.
- cluckindan 1y agoIf you wanted to e.g. log something on failures, you’d need to do something like this: chain .then(job) .catch((err) => { console.error(err); return job(); }); Otherwise failures would need to be logged by the job itself before rejecting the promise.
- egorfine 1y ago> If you need a job queue in JS type Job = () => Promise<unknown>; This is not JS.
- ameliaquining 1y agoIt's trivial to remove the type annotations.
- ameliaquining 1y agoTo make it slightly more idiomatic, you can use chain.finally(job) instead of chain.then(job, job). (All browsers have supported this API for over seven years.)