Considering the following JavaScript code:
var promise = new Promise();
setTimeout(function() {
promise.resolve();
}, 10);
function foo() { }
promise.then(foo);
In the promise implementations I've seen, promise.resolve() would simply set some property to indicate the promise was resolved and foo() would be called later during an event loop, yet it seems like the promise.resolve() would have enough information to immediately call any deferred functions such as foo().
The event loop method seems like it would add complexity and reduce performance, so why is it used?
While most of my use of promises is with JavaScript, part of the reason for my question is in implementing promises in very performance intensive cases like C++ games, in which case I'm wondering if I could utilize some of the benefits of promises without the overhead of an event loop.
A promise represents the completion of an asynchronous function. It is an object that might return a value in the future. It accomplishes the same basic goal as a callback function, but with many additional features and a more readable syntax.
Promises can be an alternative to callbacks for asynchronous APIs. Instead of passing a callback as an argument and handling the error in the same place, a promise object allows us to handle success and error cases separately and it also allows us to chain multiple asynchronous calls instead of nesting them.
JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.
They can handle multiple asynchronous operations easily and provide better error handling than callbacks and events. In other words also, we may say that, promises are the ideal choice for handling multiple callbacks at the same time, thus avoiding the undesired callback hell situation.
All promise implementations, at least good ones do that.
This is because mixing synchronicity into an asynchronous API is releasing Zalgo.
The fact promises do not resolve immediately sometimes and defer sometimes means that the API is consistent. Otherwise, you get undefined behavior in the order of execution.
function getFromCache(){
return Promise.resolve(cachedValue || getFromWebAndCache());
}
getFromCache().then(function(x){
alert("World");
});
alert("Hello");
The fact promise libraries defer, means that the order of execution of the above block is guaranteed. In broken promise implementations like jQuery, the order changes depending on whether or not the item is fetched from the cache or not. This is dangerous.
Having nondeterministic execution order is very risky and is a common source of bugs. The Promises/A+ specification is throwing you into the pit of success here.
Whether or not promise.resolve()
will synchronously or asynchronously execute its continuations really depends on the implementation.
Furthermore, the "Event Loop" is not the only mechanism to provide a different "execution context". There may be other means, for example threads or thread pools, or think of GCD (Grand Central Dispatch, dispatch lib), which provides dispatch queues.
The Promises/A+ Spec clearly requires that the continuation (the onFulfilled
respectively the onRejected
handler) will be asynchronously executed with respect to the "execution context" where the then
method is invoked.
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code. [3.1].
Under the Notes you can read what that actually means:
Here "platform code" means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.
Here, each event will get executed on a different "execution context", even though this is the same event loop, and the same "thread".
Since the Promises/A+ specification is written for the Javascript environment, a more general specification would simply require that the continuation will be asynchronously executed with respect to the caller invoking the then
method.
There are good reasons to this in that way!
Example (pseudo code):
promise = async_task();
printf("a");
promise.then((int result){
printf("b");
});
printf("c");
Assuming, the handler (continuation) will execute on the same thread as the call-site, the order of execution should be that the console shows this:
acb
Especially, when a promise is already resolved, some implementations tend to invoke the continuation "immediately" (that is synchronously) on the same execution context. This would clearly violate the rule stated above.
The reason for the rule to invoke the continuation always asynchronously is that a call-site needs to have a guarantee about the relative order of execution of handlers and code following the then
including the continuation statement in any scenario. That is, no matter whether a promise is already resolved or not, the order of execution of the statements must be the same. Otherwise, more complex asynchronous systems may not work reliable.
Another bad design choice for implementations in other languages which have multiple simultaneous execution contexts - say a multi-threaded environment (irrelevant in JavaScript, since there is only one thread of execution), is that the continuation will be invoked synchronously with respect to the resolve
function. This is even problematic when the asynchronous task will finish in a later event loop cycle and thus the continuation will be indeed executed asynchronously with respect to the call-site.
However, when the resolve
function will be invoked by the asynchronous task when it is finished, this task may execute on a private execution context (say the "worker thread"). This "worker thread" usually will be a dedicated and possibly special configured execution context - which then calls resolve
. If that resolve
function will synchronously execute the continuation, the continuation will run on the private execution context of the task - which is generally not desired.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With