The 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.
Couldn’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?
chain 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.
JavaScript 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.
I 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.
The 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.
> 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.
That's not how promises work, there's no chain. It's no different from
Except dynamicAt 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/#aWYgKCF3aW5kb3cuZ2MpIHsKICB0aHJvdyBu...
Couldn’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-han...
Why 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.
Though it seems like that, no.
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.)
[dead]
Chaining promises like this is not a good idea for high throughput cases (creates gc pressure), but perfectly valid for regular cases.
Huh?
It creates 1 object allocation per enqueue.
If you wanted to e.g. log something on failures, you’d need to do something like this:
Otherwise failures would need to be logged by the job itself before rejecting the promise.Something like this?
async function runTasks(tasks: Job[]) { for (let task of tasks) { try { await task() } catch (e) { } } }
This only works if you have the full list of tasks beforehand.
Anybody willing to walk me through this code? I don't get it.
chain 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.
JavaScript 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.
A production grade application would need a much more robust mechanism like BullMQ
I 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.
That's TS, not JS.
One line of TS, then two lines of JS (with a type annotation in one of them)
It’s not even 2 lines:
1. Type definition 2. Chain definition 3. Enqueue definition
Line 1 is fully erased when transpiling, leaving two lines of JavaScript.
I 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?
For now.
https://github.com/tc39/proposal-type-annotations
Oh, come on.