Skip to content

Instantly share code, notes, and snippets.

@ForbesLindesay
Created February 26, 2016 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ForbesLindesay/c4b0a4484f14a9aa1da5 to your computer and use it in GitHub Desktop.
Save ForbesLindesay/c4b0a4484f14a9aa1da5 to your computer and use it in GitHub Desktop.
// assume that the optimiser doesn't know the base promise is never resolved
let resolve, reject;
const base = new Promise((_resolve, _reject) => {resolve = _resolve; reject = _reject;});
const end = new Promise(resolve => {
const middlePromise = new Promise(resolve => {
setTimeout(() => resolve(base), 1000);
});
resolve(middlePromise);
});
setTimeout(() => {
// at this point, `middlePromise` has leaked. It can't be garbage collected as either `base` or `end` must
// keep a reference to it.
//
// You can either take the approach of saying that when `base` is resolved, it must resolve `middlePromise` (which in,
// turn is responisble for resolving `end`), in which case `base` must keep a reference to `middlePromise`.
//
// Or you can take the approach of saying that when new handlers are added to `end` (via `.then`), they must automatically
// be passed to `middlePromise` (then `middlePromise` must pass them on to `base`), in which case `end` must keep a reference
// to `middlePromise`.
//
// This means that `middlePromise` can never be garbage collected, even though we could guarantee that it is no longer needed
// if we could find a way to directly connect `end` to `base`.
}, 10000);
// with just one promise leaking, it's not really a problem, but there can be many `middlePromise`es
function doRecursive(i) {
const middlePromise = doAsync().then(() => {
if (i > 999999) {
return getBasePromise();
}
return doRecursive(i + 1);
});
return middlePromise;
}
const end = doRecursive(0);
// In this example, `middlePromise`es must all be kept around for the entire duration of the operation (until the stack unwinds)
// This becomes really problematic if the recursion is intended to represent an infinite loop that polls some web service.
// The sollution (without async/await) is to break the chain:
function doRecursive(i) {
return new Promise((resolve, reject) => {
function recurse(i) {
try {
// nobody keeps a reference to this `middlePromise`
const middlePromise = doAsync().then(() => {
if (i > 999999) {
resolve(getBasePromise());
} else {
recurse(i + 1);
}
}, reject);
} catch (ex) {
reject(ex);
}
}
}
recurse(i);
});
}
const end = doRecursive(0);
// this process, of keeping a reference to the return/resolve point of only the top most function in the stack, is generally
// known as "tail recursion" in synchronous programming. Here it's exactly the same process, but it's much harder to do as
// an automated build step.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment