8

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.

silentorb
  • 2,432
  • 2
  • 18
  • 20
  • 1
    If you want a promise resolved immediately, you just call `promise.resolve()` and it will call any registered resolve handlers at that moment. If you want your own event loop to unwind before the promise is resolved, then you might do `setTimeout(function() {promise.resolve()}, 10);`. But, it's up to how you want the `.resolve()` to behave. It is not required to use the `setTimeout()`. – jfriend00 May 03 '14 at 17:51
  • 1
    Yes, I understand that. What I want to understand is when using setTimeout, after 10 milliseconds foo() is not immediately called, but instead the resolution is placed on a queue and foo() is executed some time later. – silentorb May 03 '14 at 22:18
  • Which promise implementation does that? There are many different implementations so it's hard to ask a question like this without referring to a specific implementation. As far as I know, there's no specification that says resolve callbacks shouldn't be called until after a delay. – jfriend00 May 03 '14 at 22:18
  • 1
    I would be interested to know which promise implementations don't do that. Using a deferred event loop is suggested in the [Promise/A+ spec](http://promises-aplus.github.io/promises-spec/#point-66) and seems to be the assumed method. If you need an example, I'm most familiar with when.js, which uses such a loop. – silentorb May 03 '14 at 22:32
  • 2
    So, you're asking why section 2.2.4 of the Promises/A+ spec states: `Fulfilled or onRejected must not be called until the execution context stack contains only platform code. [[3.1](#notes)].`? We can guess, but you'd need someone who was actually involved in the production of the spec to explain their reasoning at the time. FYI, some of this architecture probably assumes a garbage collected language where it's easy to hang onto references to things after the stack has unwound. That is not so easy in C++ as memory management is much more difficult for async `resolve()`. – jfriend00 May 03 '14 at 22:50

3 Answers3

10

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.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    For what it's worth, here is a promise implementation for ObjectiveC I recently looks into and looks nice https://github.com/mxcl/PromiseKit it is Promises/A+ complaint – Benjamin Gruenbaum May 04 '14 at 05:33
  • 1
    PromiseKit is not at all Promise/A+ compliant. It doesn't have a public resolver API, instead one needs to use the provided Categories which implement the resolver aspect for certain Foundation classes (e.g. NSURLConnection) (or needs to implement them). Additionally, when a promise will be resolved by given categories, the continuation will be called *synchronously* which effectively executes the handler on current execution context - which may not be known to the call site. So in fact, PromiseKit will actually be that "broken implementation" you are talking about (and I do agree ;) ) – CouchDeveloper May 10 '14 at 10:26
  • 2
    PromiseKit no longer releases Zalgo. Give it a break, I released it 4 weeks ago. – mxcl May 23 '14 at 21:45
6

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.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
2

Promises are all about cooperative multitasking.

Pretty much the only method to achieve that is to use message based scheduling.

Timers (usually with 0 delay) are simply used to post the task/message into message queue - yield-to-next-task-in-the-queue paradigm. So the whole formation consisting of small event handlers works and more frequently you yield - more smoothly all this work.

c-smile
  • 26,734
  • 7
  • 59
  • 86
  • @BenjaminGruenbaum Resolve and reject methods of the promise are asynchronous by themselves. The caller shall not expect that it will be blocked for significant amount of time when calling them. Suggested immediate execution may block the caller. – c-smile May 04 '14 at 17:02
  • @BenjaminGruenbaum, as I said, caller shall not be blocked by resolve/reject operation execution. Each task is given its own time frame: caller code handles its own stuff and fulfillment code runs in separate execution frame (task, timer event handler here). Otherwise it would quite difficult to achieve reasonable multitasking. – c-smile May 04 '14 at 18:15