Possible contradiction between Promises/A+ spec and ECMAScript promises? - javascript

It is asserted the ECMAScript promises is a Promises/A+ implementation, so they have no contradictions. However, I encountered a behaviour of ecma promises which allegedly is out of line with the Promises/A+.
When we call promise1.then(onFulfilled, onRejected) to listen to the promise1's output, we get as a return value another promise (promise2). When the needed callback (onFulfilled/onRejected) was executed and it, in turn, returned some value x, the spec prescribes to resolve it with the defined [[Resolve(promise2, x)]] function. Let's suppose x happened to be a promise itself (x === promise3), then the steps must be taken is the following:
If x is a promise, adopt its state:
If x is pending, promise2 must remain pending until x is fulfilled or rejected.
If/when x is fulfilled, fulfill promise2 with the same value.
If/when x is rejected, reject promise2 with the same reason.
I wonder what if x is finally fulfilled with yet another promise (promise4) (there are not anything in the way of it, are there?). It can be concluded from the spec excerpt that promise2 must be fulfilled with promise4 too. But it is seemingly not so in the ECMAScript world:
let promise4 = new Promise((resolve) => { resolve(4) })
let promise3 = new Promise((resolve) => {
resolve(promise4);
});
let promise1 = new Promise((resolve) => {
resolve(1);
});
let promise2 = promise1.then((val) => { return promise3 });
promise2.then(val => console.log(val)); // output: 4
In the other words, promise2 is fulfilled with the promise4's value. This behaviour is like one that is defined in the spec for other thenable objects. So don't ECMAScript promises carry out expected type checking and just check whether x has then method?

Let's suppose x happened to be a promise itself, then the steps must be taken is the following: […]
No, they don't need to be taken - they only may be taken if x is a "promise". These steps are an optional ("allowed", not "required") optimisation:
Note 4:
Generally, it will only be known that x is a true promise if it comes from the current implementation. This clause allows the use of implementation-specific means to adopt the state of known-conformant promises.
ECMAScript does not treat its own Promises as "known to be conformant", ignoring these steps. They simply treat native promises like all other thenables. Given there is no way to create an ECMAScript Promise that is fulfilled with another promise, this is equivalent to directly adopting the state.

It can be concluded from the spec excerpt that promise2 must be fulfilled with promise4 too.
No, this does not follow from the Promises/A+ specification. The rule you quote from it
If/when x is fulfilled, fulfill promise2 with the same value.
...is recursive in nature. It should be understood in more elaborate terms as follows:
If/when x is fulfilled, fulfill promise2 with the same value that x fulfilled with.
Now for the part "that x fulfilled with", the same Resolution Procedure applies(!): Indeed, as x resolves with yet another thenable (promise4), it in turn gets locked-in with the next promise in the chain (promise4 in your example). This is not yet the fulfilled value. -- there is an important difference between resolving and fulfilling. This second execution of the Resolution Procedure will make sure that the value that x fulfils with, is the value that promise4 fulfils with.
The chain of locked-in promises can have any length, but the principle remains the same: each will resolve by locking into the next promise through this Promises/A+ Resolution Procedure. When the last one in this chain fulfils (with a non-thenable value), then all promises that are locked-in will get fulfilled with this value.

Related

Promise wrapping in catch clause and waiting in Promise.all

The documentation for promises states that .catch()returns a promise. Does this mean that if you return a promise in your code it will get wrapped in another promise from the .catch?
i.e.
const x = Promise.reject().catch(() => Promise.resolve("test"));
Will the above promise that resolves with the value of "test" be wrapped in another promise from the .catch? To my understanding this is what happens in async functions; they wrap whatever the result is in side of a promise "under the hood". Which could then result in double promises, is that also happening here?
If the promise in the variable x above was put into an existing array and passed to Promise.all(), what would constitute as being resolved? Would the catch block and the inner function have to complete before Promise.all resolves? If so, why is this the case? Why would Promise.all not resolve as soon as the first Promise.reject() is executed? How would it know to wait?
Yes and no ... there are multiple promises involved, but they are chained, not wrapped in each other, or in the final promise "takes on" (the actual phrase is "adopts") the value of the Promise.resolve("test") in your case, so, what you get is a single promise that settles (in this case, resolves) to the value "test"
The 5 Answers are:
yes, it is resolved,
yes, because x is the final promise returned by that expression
because x is the final promise returned by that expression, none of the other Promises are "visible" to the Promise.all,
because x is the final promise returned by that expression, the other Promises are not "visible" to x
that's how promise chains work, there's no waiting, it's just promise chaining at work
You may find The Promise Resolution Procedure - 2.3.2 helps in understanding the inner workings of Promises - 2.3.2 specifically deals with returning a Promise inside .then ... the rest of that resolution procedure is also illuminating.

Why does Promise.prototype.then always return promises?

I'd like to check my understanding of Promise.prototype.then is correct.
In the specs (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) it says:
If a handler function:
returns a value, the promise returned by then gets resolved with the
returned value as its value.
Is it not a bit strange for .then to return a promise object if I just returned something simple like 5 from the .then's callback? How does this promise object resolve? It doesn't seem like it's fetching something from anywhere. Do promises like this always get fulfilled?
function fetchDog(){
fetch("https://dog.ceo/api/breeds/image/random")
.then(response => 5)
.then(data => console.log(data))
};
fetchDog();
A call to then() must return a promise according to the Promises/A+ specification, 2.2.7 (which is also reflected in the EcmaScript specs).
At the time that then() is called on a promise A, a promise B is returned, but the then-callback is not executed at that moment. It is not known yet whether and how that promise B will settle. The then-callback will be called asynchronously, only after the base promise A, has resolved.
When A resolves, the then-callback will be called, and that call will determine how promise B will resolve. So when the callback returns 5, then that will be the value with which promise B resolves. If it is undefined, then that will be it. Only when the returned value is yet another promise C (or at least a thenable), there will be a cascade effect, and promise B will link its resolution to that of C.
The callback function passed to then can't run until the promise it is associated with is resolved, but then has to return immediately, so then returns a promise.

Node: Is behavior well-defined for Promise.all with non-Promise values?

The docs for Promise.all() describe the argument as an array of Promises. Is behavior defined if some (or all) of the elements in the array are non-Promise values? For example, in Node 6.10.2:
Promise.all([1, 2, 3]).then(
res => console.log(res)
);
Prints [ 1, 2, 3 ], as expected. Is this behavior (where Promise.all resolves with the same values that it was called with) guaranteed in Node?
Promise.all() is specified in the ES6 specification.
It appears that section 24.4.4.1.1 in that ES6 specification at step 6.i describes how each item in the iterable passed to Promise.all() is passed to a promise constructor (essentially calling Promise.resolve(item)) so that any non-promise gets wrapped in a promise that will resolve itself on the next tick.
You need to follow the overall context of how these steps work to fully understand, but the specific 6.i step is this:
Let nextPromise be Invoke(constructor, "resolve", «‍nextValue»).
Where constructor (in this context) is Promise and Invoke() is calling the resolve method on it - so it's essentially doing:
let nextPromise = Promise.resolve(nextValue);
Which is how each item in the iterable is wrapped in a promise if it wasn't already a promise.
Then, later in step 6.r, it does this:
Let result be Invoke(nextPromise, "then", «‍resolveElement, resultCapability.[[Reject]]»).
Which is where it calls .then() on nextPromise which will now work fine even if the item in the iterable was not originally a promise because it has been wrapped in a new promise.
And, in 24.4.4.5 the description of Promise.resolve(x) is as follows:
The resolve function returns either a new promise resolved with the passed argument, or the argument itself if the argument is a promise produced by this constructor.
So, you can see that the value is wrapped in a new promise if it is not already a promise.
Is this behavior (where Promise.all() resolves with the same values that it was called with) guaranteed in Node?
Yes, if the values are not promises, then it will resolve with an array of the same values. It will be a different array, but the same values in the new array. If any of the values are promises, then obviously, the final array contains the resolved value of that promise. This is guaranteed by the specification.

Building a promise chain recursively in javascript - memory considerations

In this answer, a promise chain is built recursively.
Simplified slightly, we have :
function foo() {
function doo() {
// always return a promise
if (/* more to do */) {
return doSomethingAsync().then(doo);
} else {
return Promise.resolve();
}
}
return doo(); // returns a promise
}
Presumably this would give rise to a call stack and a promise chain - ie "deep" and "wide".
I would anticipate a memory spike larger than either performing a recursion or building a promise chain alone.
Is this so?
Has anyone considered the memory issues of building a chain in this way?
Would memory consumption differ between promise libs?
a call stack and a promise chain - ie "deep" and "wide".
Actually, no. There is no promise chain here as we know it from doSomeThingAsynchronous.then(doSomethingAsynchronous).then(doSomethingAsynchronous).… (which is what Promise.each or Promise.reduce might do to sequentially execute handlers if it was written this way).
What we are facing here is a resolve chain1 - what happens in the end, when the base case of the recursion is met, is something like Promise.resolve(Promise.resolve(Promise.resolve(…))). This is only "deep", not "wide", if you want to call it that.
I would anticipate a memory spike larger than either performing a recursion or building a promise chain alone.
Not a spike actually. You'd slowly, over time, build a bulk of promises that are resolved with the innermost one, all representing the same result. When, at the end of your task, the condition is fulfilled and the innermost promise resolved with an actual value, all of these promises should be resolved with the same value. That would end up with O(n) cost for walking up the resolve chain (if implemented naively, this might even be done recursively and cause a stack overflow). After that, all the promises except for the outermost can become garbage collected.
In contrast, a promise chain that is built by something like
[…].reduce(function(prev, val) {
// successive execution of fn for all vals in array
return prev.then(() => fn(val));
}, Promise.resolve())
would show a spike, allocating n promise objects at the same time, and then slowly resolve them one by one, garbage-collecting the previous ones until only the settled end promise is alive.
memory
^ resolve promise "then" (tail)
| chain chain recursion
| /| |\
| / | | \
| / | | \
| ___/ |___ ___| \___ ___________
|
+----------------------------------------------> time
Is this so?
Not necessarily. As said above, all the promises in that bulk are in the end resolved with the same value2, so all we would need is to store the outermost and the innermost promise at one time. All intermediate promises may become garbage-collected as soon as possible, and we want to run this recursion in constant space and time.
In fact, this recursive construct is totally necessary for asynchronous loops with a dynamic condition (no fixed number of steps), you cannot really avoid it. In Haskell, where this is used all the time for the IO monad, an optimisation for it is implemented just because of this case. It is very similar to tail call recursion, which is routinely eliminated by compilers.
Has anyone considered the memory issues of building a chain in this way?
Yes. This was discussed at promises/aplus for example, though with no outcome yet.
Many promise libraries do support iteration helpers to avoid the spike of promise then chains, like Bluebird's each and map methods.
My own promise library3,4 does feature resolve chains without introducing memory or runtime overhead. When one promise adopts another (even if still pending), they become indistinguishable, and intermediate promises are no longer referenced anywhere.
Would memory consumption differ between promise libs?
Yes. While this case can be optimised, it seldom is. Specifically, the ES6 spec does require Promises to inspect the value at every resolve call, so collapsing the chain is not possible. The promises in the chain might even be resolved with different values (by constructing an example object that abuses getters, not in real life). The issue was raised on esdiscuss but remains unresolved.
So if you use a leaking implementation, but need asynchronous recursion, then you better switch back to callbacks and use the deferred antipattern to propagate the innermost promise result to a single result promise.
[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]: undocumented playground, passes aplus. Read the code at your own peril: https://github.com/bergus/F-Promise
[4]: also implemented for Creed in this pull request
Disclaimer: premature optimization is bad, the real way to find out about performance differences is to benchmark your code, and you shouldn't worry about this (I've only had to once and I've used promises for at least 100 projects).
Is this so?
Yes, the promises would have to "remember" what they're following, if you do this for 10000 promises you'd have a 10000 long promise chain, if you don't then you won't (for example, with recursion) - this is true for any queueing flow control.
If you have to keep track of 10000 extra things (the operations) then you need to keep memory for it and that takes time, if that number is a million it might not be viable. This varies among libraries.
Has anyone considered the memory issues of building a chain in this way?
Of course, this is a big issue, and a use case for using something like Promise.each in libraries like bluebird over thenable chaining.
I've personally had in my code to avoid this style for a quick app that traverses all the files in a VM once - but in the vast majority of cases it's a non issue.
Would memory consumption differ between promise libs?
Yes, greatly. For example bluebird 3.0 will not allocate an extra queue if it detects a promise operation is already asynchronous (for example if it starts with a Promise.delay) and will just execute things synchronously (because the async guarantees are already preserved).
This means that what I claimed in my answer to the first question isn't always true (but is true in the regular use case) :) Native promises will never be able to do this unless internal support is provided.
Then again - it's no surprise since promise libraries differ by orders of magnitude from one another.
I just came out a hack that may help solving the problem: don't do recursion in the last then, rather, do it in the last catch, since catch is out of the resolve chain. Using your example, it would be like this:
function foo() {
function doo() {
// always return a promise
if (/* more to do */) {
return doSomethingAsync().then(function(){
throw "next";
}).catch(function(err) {
if (err == "next") doo();
})
} else {
return Promise.resolve();
}
}
return doo(); // returns a promise
}
To complement the awesome existing answers I'd like to illustrate the expression, which is the result of such an asynchronous recursion. For the sake of simplicity I use a simple function that computes the power of a given base and exponent. The recursive and base case are equivalent to those of the OP's example:
const powerp = (base, exp) => exp === 0
? Promise.resolve(1)
: new Promise(res => setTimeout(res, 0, exp)).then(
exp => power(base, exp - 1).then(x => x * base)
);
powerp(2, 8); // Promise {...[[PromiseValue]]: 256}
With the help of some substitution steps the recursive portion can be replaced. Please note that this expression can be evaluated in your browser:
// apply powerp with 2 and 8 and substitute the recursive case:
8 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 8)).then(
res => 7 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 7)).then(
res => 6 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 6)).then(
res => 5 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 5)).then(
res => 4 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 4)).then(
res => 3 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 3)).then(
res => 2 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 2)).then(
res => 1 === 0 ? Promise.resolve(1) : new Promise(res => setTimeout(res, 0, 1)).then(
res => Promise.resolve(1)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2)
).then(x => x * 2); // Promise {...[[PromiseValue]]: 256}
Interpretation:
With new Promise(res => setTimeout(res, 0, 8)) the executor is invoked immediately and executes a non-bllocking computation (mimicked with setTimeout). Then an unsettled Promise is returned. This is equivalent with doSomethingAsync() of the OP's example.
A resolve callback is associated with this Promise via .then(.... Note: The body of this callback was substituted with the body of powerp.
Point 2) is repeated and a nested then handler structure is build up until the base case of the recursion is reached. The base case returns a Promise resolved with 1.
The nested then handler structure is "unwound" by calling the associated callback correspondingly.
Why is the generated structure nested and not chained? Because the recursive case within the then handlers prevents them from returning a value until the base case is reached.
How can this work without a stack? The associated callbacks form a "chain", which bridges the successive microtasks of the main event loop.
This promise pattern will generate a recursive chain. So, each resolve() will create a new stack frame (with its own data), utilizing some memory. This means that large number of chained functions using this promise pattern can produce stack overflow errors.
To illustrate this, I'll use a tiny promise library called Sequence, which I've written. It relies on recursion to achieve sequential execution for chained functions:
var funcA = function() {
setTimeout(function() {console.log("funcA")}, 2000);
};
var funcB = function() {
setTimeout(function() {console.log("funcB")}, 1000);
};
sequence().chain(funcA).chain(funcB).execute();
Sequence works great for small/medium sized chains, in the range of 0-500 functions. However, at about 600 chains Sequence starts degradating and generating often stack overflow errors.
The bottom line is: currently, recursion-based promise libraries are more suitable for smaller/medium sized function chains, while reduce-based promise implementations are ok for all cases, including larger chains.
This of course doesn't mean that recursion-based promises are bad. We just need to use them with their limitations in mind. Also, it's rare that you'll need to chain that many calls (>=500) via promises. I typically find myself using them for async configurations which utilize heavily ajax. But even if the most complex cases I haven't seen a situation with more than 15 chains.
On a side note...
These statistics were retrieved from tests performed with another of my libraries - provisnr - which captures the achieved number of function calls within a given interval of time.

Accessing a previously fulfilled promise result in a promises chain [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 8 years ago.
What is the correct pattern, when coding with promises, to access data coming from long before in a chain of promises?
For example:
do_A.then(do_B).then(do_C).then(do_D).then(do_E_WithTheDataComingFrom_A_And_C_OnlyWhen_D_IsSuccesfullyCompleted)
My current solution: passing along a single JSON structure through the chain, and let each step populate it.
Any opinion about that?
I don't think there's one "correct" pattern for this. Your solution sounds neat, however, it's a bit tightly coupled. It may work great for your situation, but I see a few problems with it as a general pattern:
Participating steps need to agree on the structure of the collector object.
Every step needs to participate in at least the forwarding of the object, which can get tedious if the chain is long and the need for previous data occurs sporadically. This is also inflexible to insertion of steps not written by you (chaining doesn't always happen linearly).
Said differently: Unless do_A|B|C|D and do_E are under your control, you'll need to wrap them with boilerplate to store off your collector object in a closure and convert to and from the functions' natural inputs and results, since the functions wont be "in on" your pattern.
On the other hand, if the functions are in on it, then the data-dependencies between the steps have effectively become hidden inside the functions. This may look clean, but it could become a maintenance problem. For example: if this is a team project, then someone might think they can re-order your steps, absent any no clue in the call-pattern what inputs do_E requires.
I would suggest a more straightforward approach using closures:
var a, c;
do_A()
.then(function(result) { a = result; return do_B(); })
.then(do_C)
.then(function(result) { c = result; return do_D(); })
.then(function() {
return do_E_WithTheDataComingFrom_A_And_C_OnlyWhen_D_Succeeds(a, c);
})
.catch(failed);
There's no collector object to define; do_A|B|C|D and do_E can be generic functions without knowledge of any pattern; there's no boilerplate unless returned data is relied on (do_B and do_D); and the data-dependencies (a and c) are explicit.
Yes, this is the correct way to chain state with actions.
Chaining .then statements is very common and is usually our building block when piping things around. It's at the very core of promises.
What you're doing is both correct and idiomatic.
For the curious spirit let's show this.
In order to verify this - we can check the promises specification.
We want to verify that:
It chains
In the case of a rejection, it doesn't call the handler in the chained then
It rejects the next promise returned from then with the same reason
It executes in sequence passing return value.
Let's verify these in order, using the specification - in particular .then:
1. It chains
7.1 then must return a promise [3.3].
Great, let's verify that it also chains on fullfillment
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure >[[Resolve]](promise2, x).
Great, so we know that when our promise resolves or rejects then our then handler is called with the appropriate parameter. So .then(do_A).then(do_B) will always work assuming do_A resolves.
2. In the case of a rejection, it doesn't call the handler in the chained then
7.iv. If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason.
Great, so it rejects and calls onRejected if it's there, if it doesn't it chains.
3. It rejects the next promise returned from then with the same reason
We just covered that in 2.
4. It executes in sequence passing return value.
That is again
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
So, if you set onFulfilled it'll run the resolution process. The resolution process itself dictates:
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.
If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
Where y is the return value of x.
Great! so it works.

Categories

Resources