call stack and promise chain - that is, deep and wide.
No, actually. There is no disadvantaged chain here, since we know it from doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).โฆ (which Promise.each or Promise.reduce can do to execute handlers sequentially if it was written this way).
What we are facing here is decision chain 1 - what happens at the end when a basic case of recursion occurs, something like Promise.resolve(Promise.resolve(Promise.resolve(โฆ))) . It is only โdeep,โ not โwide,โ if you want to call it.
I would expect a burst of memory to be more than doing a recursion or creating a single chain of promises.
This is actually not a thorn. You gradually, over time, create most of the promises that are resolved from the innermost, all present the same result. When the condition is fulfilled at the end of your task and the internal promise is resolved with the actual value, all these promises must be resolved with the same value. This will result in an O(n) cost for moving along the permission chain (if implemented naively, it can even be recursive and cause a stack overflow). After that, all promises, except the most external, can become garbage collected.
On the contrary, a promise chain created by something like
[โฆ].reduce(function(prev, val) { // successive execution of fn for all vals in array return prev.then(() => fn(val)); }, Promise.resolve())
will show a splash, highlighting objects n both at the same time, and then slowly resolve them one by one, garbage collects the previous ones until there is only the promised promise of the end.
memory ^ resolve promise "then" (tail) | chain chain recursion | /| |\ | / | | \ | / | | \ | ___/ |___ ___| \___ ___________ | +----------------------------------------------> time
This is true?
Not necessary. As stated above, all promises in this volume are ultimately resolved with the same value of 2, so we only need to keep the most external and internal promise at a time. All intermediate promises can be collected as soon as possible, and we want to run this recursion in constant space and time.
In fact, this recursive construction is absolutely necessary for agastra cycles with a dynamic condition (without a fixed number of steps), you cannot avoid this. In Haskell, where it is used all the time for the IO monad, the optimization for it is implemented only because of this case. It is very similar to tail call recursion , which is usually eliminated by compilers.
Has anyone considered memory issues when constructing a circuit this way?
Yes. This was discussed in promises / aplus , but, nevertheless, there was no result.
Many promise libraries support iterative helpers to avoid chains of then promises, such as the Bluebird each and map methods.
My own promise library 3.4 implements a chain of decisions without entering memory overhead or runtime. When one promise accepts another (even if it has not yet been accepted), they become indistinguishable, and intermediate promises are no longer referenced anywhere.
Will memory consumption vary between both libs?
Yes. Although this case can be optimized, this is rarely the case. In particular, the ES6 specification requires promises to check the value with each call to resolve , so chain folding is not possible. promises in a chain can even be resolved with different values โโ(by creating an example object that abuses getters, rather than in real life). The problem was raised by esdiscuss , but remains unresolved.
So, if you use implementation leaks, but need asynchronous recursion, then it is better to switch to callbacks and use deferred antipatterns to distribute the secret result promises a single result.
[1]: no official terminology
[2]: well, they are resolved with each other. But we want to resolve them with the same value, we expect that [3]: an undocumented playground, passes aplus. Read the code at your own risk: https://github.com/bergus/F-Promise
[4]: also implemented for Creed in this transfer request