Created
February 26, 2016 15:23
-
-
Save ForbesLindesay/c4b0a4484f14a9aa1da5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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