Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do Promise libraries use event loops?

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.

like image 746
silentorb Avatar asked May 03 '14 17:05

silentorb


People also ask

How does promise work in 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.

What are some of the benefits of promises over event driven programming?

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.

What does JavaScript event loop do?

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.

Why are promises better than callback?

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.


2 Answers

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.

like image 198
Benjamin Gruenbaum Avatar answered Sep 19 '22 07:09

Benjamin Gruenbaum


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.

  1. onFulfilled or onRejected 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.

like image 39
CouchDeveloper Avatar answered Sep 19 '22 07:09

CouchDeveloper