Loss of interest in promise resolution + aborting deferreds

576 views
Skip to first unread message

Igor Minar

unread,
Nov 18, 2011, 2:10:43 AM11/18/11
to q-con...@googlegroups.com
Hi there,

I've been studying deferred and promises for a while as well as their (mostly flawed) implementations I came across in various libraries/frameworks.

There are two things I struggle with and would be grateful to hear your opinion on:

1. how a loss of interest in promise resolution should be handled and
2. if such a event could be a signal for the owner of the deferred object to abort its operation

The real world use-case I have is this one:

function clickHandler(url) {
  fetchData(url).then(updateUi);
}

where clickHandler is attached to multiple links on a web page and fetchData makes an http/xhr call to the server and returns a promise.

Now let's say that the user clicks on one link and before the data is received clicks on the second link. Since I know that the user is not interested in the result of the first click action, I don't want to update the UI when the first fetchData promise is resolved. This is important, because it's not rare for the second request to return before the first one, in which case the UI would display the data for the second click and only then be updated with the data for the first click. This results in a bad UX and all kinds of race condition bugs.

So what I'm really asking for is a way to deregister or disable the updateUi callback when I know that executing it is inappropriate.

Related to this, is my second question. Could this kind expression of loss of interest in the resolution be a signal for the  fetchData function to, at its own discretion and only if no other callbacks are awaiting resolution, abort its operation (in this case abort the xhr request)?

In the past when working with a callback-style api I typically used a pair of closure and local variables to determine if the updateUi action should be executed or not:

var requestedUrl;

function clickHandler(url) {
  requestedUrl = url;
  fetchData(url, function(data) {
    if (url === requestedUrl) updateUi(data);
  });
}

This is far from ideal.

What I think I want is something like this:

var uiUpdated;

function clickHandler(url) {
  uiUpdated.cancel(); // ignored if already fulfilled
  uiUpdated = fetchData(url).then(updateUi);
}

This would cause the updateUi callback to be deregistered from the fetchData's promise, and it's corresponding errback (the default errback in this case) to be called with "canceled" as the rejection reason. This rejection would then propagate down to uiUpdated promise. Optionally this would also signal the fetchData via its deferred that all parties previously interested in the resolution of its promise lost their interest and it can decide to continue or abort its operation.

Yes, it's weird that I'm calling cancel on the derived promise, rather than on the original promise I registered my callback with, but that's the only way how I can identify the callback that I want to cancel.

There are many interesting situations that this kind of "cancelation" (for a lack of a better word) results in, especially when we have a nontrivial chain of promises with forks and joins, and a promise somewhere in the middle gets canceled, but let's discuss this simple case first.

Does this sound like a good idea or are there other better ways that can be used to deal with my use-case?

Thanks,
Igor


Kris Kowal

unread,
Nov 18, 2011, 2:09:42 PM11/18/11
to q-con...@googlegroups.com
I’ve been contemplating this as well.

A year or two ago, I brought this issue up with Mark Miller in a chat.
He strongly discouraged tacking this functionality onto promises. The
notion is that promises, when used as a security abstraction,
guarantee that information can only flow in one direction. So you can
safely give a promise to a gaggle of mutually-suspicious consumers and
give the corresponding resolve function to a skein of
mutually-suspicious providers. With a more rigorously
security-conscious implementation of Q than mine, you are then
guaranteed that only one provider will be able to send one message to
a consumer. All other providers can call resolve all they want and
none of them will know which of the others successfully sent their
value. All of the consumers can observe the promise and are equally
unaware of each-other or where the value came from.

When we introduce cancellation to promises, it is then possible for a
consumer to communicate with a provider, a tiny message. But a one-bit
serial channel of information is sufficient to communicate.

I am at least convinced in my own work that we need to be able to
cancel promises, but I am not sure how to go about it without losing
good properties. It might be possible to bifurcate the system into
secure and insecure components with an identical API. In that case,
abort messages might get intercepted at some gate and discarded to
prevent cross-talk.

Perhaps Mark Miller can weigh in on this in detail.

Kris

Igor Minar

unread,
Nov 18, 2011, 5:12:29 PM11/18/11
to q-con...@googlegroups.com
I think the communication is bidirectional only if the deferred is notified about the loss of interest.

If only the first part (deregistration of the callback and rejection of *my* promise) was implemented, then the communication would still be unidirectional. 

In my scenario:

var uiUpdated;

function clickHandler(url) {
  uiUpdated.cancel(); // ignored if already fulfilled
  uiUpdated = fetchData(url).then(updateUi);
}

calling cancel on uiUpdated would deregister updateUi from the fetchData deferred but would not let fetchData deferred's owner know that nobody is listening any more. However when updateUi is being deregistered, its corresponding errback should be called so that any promise derived from uiUpdated is rejected (or resolved, if I registered a custom errback that would recover from the cancelation).

Would this still break any of the important invariants of deferreds/promises?

/i

Igor Minar

unread,
Nov 18, 2011, 11:20:29 PM11/18/11
to q-con...@googlegroups.com
One major flaw of the cancel method is that it allows for not only deregistration/cancelation of my callback, but also of the callback registered on the parent promise.

In my example, I'm able to cancel fetchData's promise by getting a reference to it and calling cancel on this promise:

var dataFetched;

function clickHandler(url) {
  dataFetched.cancel(); // oh no! I'm interfering with the internal state of the fetchData function
  var dataFetched = fetchData(url);
  dataFetched.then(updateUi);
}


The only way I can think of to secure the right to call cancel on a promise, is by requiring the parent promise to be passed as an argument to the cancel method:

var uiUpdated, dataFetched;

function clickHandler(url) {
  uiUpdated.cancel(fetchDataPromise); // ignored if already fulfilled
  dataFetched = fetchData(url);
  uiUpdated = dataFetched.then(updateUi);
}

now this api is getting supper ugly.. and there is still one major flaw. The first time click handler is called, I have to check if uiUpdated is defined at all:

var uiUpdated, dataFetched;

function clickHandler(url) {
  if (uiUpdated) uiUpdated.cancel(dataFetched); // ignored if already fulfilled
  dataFetched = fetchData(url);
  uiUpdated = dataFetched.then(updateUi);
}

at this point I might as well just use my current counter based ignore method without employing cancel at all:

var counter = 0;

function clickHandler(url) {
  var myCounter = ++counter;
  fetchData(url).then(function(value) {
    if (myCounter==counter) updateUi(value);
  });
}

And I could even avoid the global counter by adding a property onto the clickHandler function:

function clickHandler(url) {
  var myCounter = ++clickHandler.counter;
  fetchData(url).then(function(value) {
    if (myCounter==counter) updateUi(value);
  });
}
clickHandler.counter = 0;

This is not pretty, but at least it feels less hacky than the cancel snippet above.

Thoughts?

/i

Kris Kowal

unread,
Dec 19, 2011, 10:45:21 PM12/19/11
to q-con...@googlegroups.com
It strikes me that cancellation must signal that you are no longer
interested in the result, and that some internal mechanism must count
how many promises are interested in the result of a deferred promise.
As in a reference counting system, when that number drops to none,
that would fire some attached cancellation routine, registered on the
deferred, and possibly propagate a new loss of interest to other
promises.

I took a stab at a cancelable "delay" call (clearTimeout of the
setTimeout) with decent success in the trivial case, but not in the
case of canceling a derived promise, which would need to propagate
that disinterest backward.

I should bring up that Mark Miller told me a year ago to not pursue
cancelable promises because it would break a fundamental security
property of promises: that information can and should only travel one
direction: from the resolver to the promise, not from promise to
promise and not from resolver to resolver. Deciding to ignore
subsequent calls to deferred.resolve() rather than throwing an error
was a decision guided by Tyler Close to prevent information from
flowing from resolver to resolver. Cancellation allows information to
flow from promise to promise, backward. I still don’t fully understand
the argument, so that is as good as I can tell. In any case, Mark
suggests that cancellation should be accommodated by another means.

I think that implies that any cancelable action would need to return
{promise, cancel} objects instead of simply returning promises, which
is not exactly ergonomic. In any case, cancellables are frequently
requested, I have wanted them, and I know that the NodeJS core team is
also looking into cancellation: they are considering putting their old
brand of promises (to be given another name like Handle to avoid a
holy war) back in core to facilitate this, e.g.,

var stat = FS.stat("foo.txt", cb(error, value)); // to retain compatibility
stat.cancel();

var stat = FS.stat("foo.txt");
stat.on("success", cb(value));
stat.on("error", eb(error));
stat.cancel();

I’ve already intimated to Isaac that event emitters could be trivially
made into thenables, which would make it possible to trivially
assimilate them in Q.

Handle.prototype.then = function (cb, eb) {
this.on("success", cb);
this.on("error", eb);
};

Q.when(FS.stat("foo.txt"), cb, eb);

Anyhow: cancellation. Keep thinking. I will too.

Kris Kowal

Domenic Denicola

unread,
Dec 19, 2011, 11:10:42 PM12/19/11
to q-con...@googlegroups.com
In our work using Q promises, only a few uses cases for cancellation have
come up. In most cases what was desired was to signal to the promise source
that the promise should never be fulfilled/broken. So if one part of the UI
started an async operation, we didn't need to explicitly cancel that
operation, but instead just needed to make sure the completion handler(s)
for that operation were never called.

---

More concretely, we had one system like the following:

var promise = downloader.downloadAsync("book id");

// ... later, in response to UI action
downloader.cancelDownload(promise);

This was implemented inside downloader by keeping a collection of cancelled
promises, and upon completion of the download, only resolving/rejecting the
corresponding deferred if that collection did not contain that deferred's
promise.

---

Another, somewhat different use case involved offering the user a dialog
with some choices:

showDialogAsync().then(
function (choice) {
// process choice
},
function (error) {
alert("Server sided error retrieving choices to display");
}
);

If the user simply clicked "Cancel" or the close button, we wanted neither
of these handlers to run. This was solved by simply never resolving or
rejecting the deferred, but I suppose that means we have some
waiting-for-resolution promises floating around in memory. Telling the
deferred "we don't care anymore" might help, memory-wise. (I suppose the
same concerns apply to my previous example.)

---

In general I think deferreds are the right place to inject cancellation
functionality. Since an API doesn't normally expose deferreds directly, the
security guarantees are not lost in the normal case. If the promise-producer
wants to expose cancellation functionality in a specific case, he can do so,
either via a separate method like the downloader example above, or perhaps
by just doing deferred.promise.cancel = deferred.cancel before returning
deferred.promise. This also allows case-by-case injection of additional
cancellation code into the exposed cancellation method.

Thus the API change would simply be the addition of deferred.cancel(), with
the following semantics:
* If the deferred is already resolved or rejected, noop.
* Otherwise, guarantee that future calls to deferred.reject and
deferred.resolve are noops.

This seems somewhat similar to jQuery 1.7's $.Callback.prototype.disable(),
where $.Callback appears to be the combination of a fuzzy understanding of
promises with an active imagination.

Best,
-Domenic

Kris Kowal

unread,
Dec 19, 2011, 11:16:29 PM12/19/11
to q-con...@googlegroups.com
Domenic,

The purpose I have in mind for cancellation is to prevent large
amounts of work to be wasted. For example, if I have requested a
stream of 1000 records and I then discover I am interested in a
completely different set of 1000 records, and perhaps this interest is
a very wild and varying thing, I will want to cancel things
frequently. If this is the case, guaranteeing that callbacks are not
called is merely a side-effect of disinterest. However, the point
about needing to prevent callbacks is interesting.

Kris Kowal

Domenic Denicola

unread,
Dec 19, 2011, 11:28:04 PM12/19/11
to q-con...@googlegroups.com
Yes, this does seem like the more conventional use case; I was just sharing
what had come up in our experience using promises.

In my proposal this would be achieved with something like
https://gist.github.com/1500248 or, perhaps less securely but more
concisely, https://gist.github.com/1500255

-----Original Message-----
From: q-con...@googlegroups.com [mailto:q-con...@googlegroups.com] On
Behalf Of Kris Kowal
Sent: Monday, December 19, 2011 23:16
To: q-con...@googlegroups.com
Subject: Re: [Q] Loss of interest in promise resolution + aborting deferreds

Kris Kowal

unread,
Dec 19, 2011, 11:32:46 PM12/19/11
to q-con...@googlegroups.com
I would like to pose a thought experiment.

Suppose that you have a memoized function that returns a promise.

var foos = {};
function foo(a) {
if (!foos[a]) {
foos[a] = fooActual(a);
}
return foos[a];
}

In this case, multiple observers will receive the same promise object.
These observers should not have the power to interfere with each
other.

Meaning, if Alice calls foo(1) and then Bob calls foo(1).cancel()
before Alice’s promise is resolved, work must not be cancelled such
that Alice still receives foo(1)’s resolution. For the purposes of
this example, Charlie is out to lunch.

Given that every call to "foo", in this very common pattern, returns
the same result, it is impossible to distinguish Alice and Bob’s need
for resolution, so as such cancellation necessarily applies to both.
This could perhaps be mitigated by explicitly rewrapping the returned
promise for each individual consumer, but at best, cross-cancellation
would become a hazard that people would have to attend to.

Kris Kowal

Domenic Denicola

unread,
Dec 19, 2011, 11:39:57 PM12/19/11
to q-con...@googlegroups.com
Completely agree. That is why I think, by default, *promises* should not be
cancellable, and if an API wants to produce a cancellable promise, the API
writer should have to opt-in and thus think hard about exactly how
cancelling works in their case.

In many instances promises will not be shared among more than one observer,
so a simple deferred.promise.cancel = deferred.cancel might suffice. But for
ones where that is expected, the promise-producer would need to (manually)
implement reference counting of some sort.

-----Original Message-----
From: q-con...@googlegroups.com [mailto:q-con...@googlegroups.com] On
Behalf Of Kris Kowal
Sent: Monday, December 19, 2011 23:33
To: q-con...@googlegroups.com
Subject: Re: [Q] Loss of interest in promise resolution + aborting deferreds

Nathan Stott

unread,
Dec 19, 2011, 11:42:33 PM12/19/11
to q-con...@googlegroups.com
Each call to the `then` method must produce a promise with a `cancel` method that remembers the callbacks that were applied with that `then`. The `cancel` method returns a promise that the callbacks will not be called. 

As an implementation detail if there are no interested observers left, we can cancel the asynchronous operation. If there are observers left we will not cancel the asynchronous operation. If there are observers added in the future,
the implementation must be able to restart the operation so that it can have a shot to fulfill the promise for the new observers.

Reference counting and being able to restart an operation are a higher barrier to promise producers though, but if you need cancellable promises then that may be the answer.

Nathan Stott

unread,
Dec 19, 2011, 11:57:00 PM12/19/11
to q-con...@googlegroups.com
On second though, restartability should not really be required.  The concern about information being leaked by a promise being rejected because another agent caused a cancellation may have a purpose for securability, but it should not be a constraint of a promise implementation.  A promise implementation should be free to choose heuristics for determining rogue agents if it would like as a method of solving the 'information leak' problem. 

The important engineering principal of promises is that all faults may be resolved locally without requiring obligations from other agents or having obligations imposed on the agent itself by outside agents.  A promise provider should be free to choose to do any of the following:

* reject future agents after a cancellation (call their errback and say the operation could not be completed)
* restart the operation that has been cancelled to provide potential resolution for the future agents that wish to observe
* use heuristics to determine rogue agents and continue asynchronous operations despite a rogue agents cancellation but do not call the rogue agents callback or errback

Igor Minar

unread,
Dec 20, 2011, 11:42:15 AM12/20/11
to q-con...@googlegroups.com
On Mon, Dec 19, 2011 at 8:42 PM, Nathan Stott <nrs...@gmail.com> wrote:
Each call to the `then` method must produce a promise with a `cancel` method that remembers the callbacks that were applied with that `then`. The `cancel` method returns a promise that the callbacks will not be called. 

I think that the error callback should be called when you cancel. That would give you an option to resolve the promises deferred from your promise. This would be useful in situations when you obtained the answer that your promise was supposed to give via an alternative means before the promise was resolved.

/i

Igor Minar

unread,
Dec 20, 2011, 11:58:36 AM12/20/11
to q-con...@googlegroups.com
I agree that communicating loss of interest as opposed to intent to cancel the operation I requested is a more common scenario in my apps.

But as I demonstrated via examples in my previous emails, this (expressing "don't call my success cb") can be done with a local counter (or flag for non-reentrant functions). This is not ideal and having a cancel method would make the code easier to read. However the downside is that the cancel method would reside on the derived promise, which means that anyone whom you share this derived promise with will be able to cancel *your* promise.

D+P -> (cb1, eb1) -> P1
                      ^
                    the cancel method for cb1 and eb1 registered with promise P resides on P1

This breaks many of the security guarantees that deferred and promises bring to the table. When javascript will have multi-return functionality, we'll be able to return P1 and C1 (cancel fn for cb1, eb1), but in the meantime, I can't think of a way to expose the cancel fn without doing so via the derived promise.

/i

Nathan Stott

unread,
Dec 20, 2011, 11:59:05 AM12/20/11
to q-con...@googlegroups.com
Are you talking about the errback of other agents or of the agent that induced the cancel?

Nathan Stott

unread,
Dec 20, 2011, 12:01:52 PM12/20/11
to q-con...@googlegroups.com
Perhaps we should rethink calling this a 'cancel' operation at all.  Perhaps it should be thought of as an 'unsubscribe' and the promise provider is responsible for cancelling the async operation if it so chooses as an implementation detail.

Igor Minar

unread,
Dec 20, 2011, 12:24:08 PM12/20/11
to q-con...@googlegroups.com
On Tue, Dec 20, 2011 at 8:59 AM, Nathan Stott <nrs...@gmail.com> wrote:
Are you talking about the errback of other agents or of the agent that induced the cancel?

errback of the promise you are trying to cancel. from there either the error will spread to derived promises unless someone recovers from it.

/i

Igor Minar

unread,
Dec 20, 2011, 12:26:49 PM12/20/11
to q-con...@googlegroups.com
I was toying with the name "abandon" and "abandonable" promises.

But as I said, unless there is a way to share this method by other means than attaching it to the derived promise, then no matter what its called, it's going to create a security leak.

/i

Nathan Stott

unread,
Dec 20, 2011, 12:31:14 PM12/20/11
to q-con...@googlegroups.com
I don't understand how that creates a security leak.  The `then` method returns a unique promise object that it is the choice of the caller to share or not to share.  Different agents making different calls to `then` can receive objects with unique cancellation functions.

Mark S. Miller

unread,
Dec 20, 2011, 12:28:07 PM12/20/11
to q-con...@googlegroups.com, Kris Kowal, Tyler Close
Hi Kris, Tyler, all,

I just committed to the Caja source tree the makeQ.js module, at <http://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/makeQ.js>, which is my attempt to implement <http://wiki.ecmascript.org/doku.php?id=strawman:concurrency> both correctly, securely, and adequately for the needs of Dr.SES. A key difference is that its extension points, Q.makeFar() and Q.makeRemote(), distinguish making a resolved far reference from an unresolved remote reference, which seems necessary to enforce the state diagram at <http://wiki.ecmascript.org/doku.php?id=strawman:concurrency#promises_and_promise_states>. I have yet to understand the security implications of the lack of this distinction in the other libraries.

As an example of the power of the Q approach (whether makeQ.js, ref_send, or qcomm), building on makeQ, at <http://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/makeSimpleAMDLoader.js#98> I implement a subset of the AMD module loader API in almost no code at all. On an adequately ES5 conformant browser, if you visit <http://google-caja.googlecode.com/svn/trunk/src/com/google/caja/ses/explicit.html> and the last line says "AMD loader test...succeeded" then the example above seems to work. However, the testing to date has been very light. Are there any extensive Q test suites I might be able to adapt?

(The next draft revision of this, currently under review, is at <http://code.google.com/p/es-lab/source/browse/trunk/src/ses/>.)

I would like to better understand the differences between makeQ, ref_send, qcomm, bcap <https://sites.google.com/site/belayresearchproject/bcap>, and caja-captp <http://code.google.com/p/caja-captp/>. I would like see makeQ and Dr.SES eventually able to interoperate with more of these. Even better would be to converge more of the advantages of these various systems into one library.

As Kris indicates, in makeQ I am taking a very principled stance on information flow, as Tyler does in ref_send. Later I hope to find the time to address the concerns with cancellation which have been raised in this thread. My belief, yet to be demonstrated, is that these concerns can be addressed well with simple patterns involving multiple promises.

-- 
  Cheers,
  --MarkM

Igor Minar

unread,
Dec 20, 2011, 12:31:53 PM12/20/11
to q-con...@googlegroups.com
hmm... just a crazy idea:

var cancel = Q.cancel();
var promise = doAsyncWork();

promise.then(cb, eb, cancel);
cancel();
// eb will be called in the next turn with rejection "canceled" even if the promise has been already resolved


notes: 
- I'm not set on the "cancel" name. 
- Maybe the last param should be an array so that we don't pollute the then signature with too many optional args and make it difficult to add progress callback in the future.

/i

Nathan Stott

unread,
Dec 20, 2011, 12:34:34 PM12/20/11
to q-con...@googlegroups.com
I don't like the idea of a global cancel for a promise.  That does create security leaks.

What is wrong with the 'cancellation' being purely for agents to say they are no longer interested in having their callback or errback called and it being up to the promise provider to determine whether or not to actually 'cancel' the operation based upon reference counting or other heuristics?

Igor Minar

unread,
Dec 20, 2011, 12:51:08 PM12/20/11
to q-con...@googlegroups.com
On Tue, Dec 20, 2011 at 9:34 AM, Nathan Stott <nrs...@gmail.com> wrote:
I don't like the idea of a global cancel for a promise.  That does create security leaks.

it's not global. the Q.cancel would be just a factory function for cancel functions and would create a new instance on each call. 

What is wrong with the 'cancellation' being purely for agents to say they are no longer interested in having their callback or errback called and it being up to the promise provider to determine whether or not to actually 'cancel' the operation based upon reference counting or other heuristics?

that's what my original proposal stated. I don't think it's wrong. I just think that it's a distraction because we have to figure out signaling the loss of interest first.

Nathan Stott

unread,
Dec 20, 2011, 12:54:30 PM12/20/11
to q-con...@googlegroups.com
I meant global as in your example appears to 'cancel' the promise for the subsequen .then and any other .then's.  I would prefer that each result of .then be considered a promise for a specific agent that can signal its lack of interest in having itself resolved.

var p = doSomethingAsync();

var agent1 = p.then(success, err);
var agent2 = p.then(success, err);

agent1.cancel();

// agent2 will still have its success and err called as appropriate.

Nathan Stott

unread,
Dec 20, 2011, 1:14:16 PM12/20/11
to q-con...@googlegroups.com
Mark, does your stance about the flow of information derive from promise theory or does it derive from the JavaScript concurrency model being a shared-nothing model and wishing to encourage that?

Igor Minar

unread,
Dec 20, 2011, 1:19:26 PM12/20/11
to q-con...@googlegroups.com
but that means that anyone with access to the promise can cancel the execution of its success callback:

var worker = initializeWorkerAsync(success, err);

// elsewhere:
worker.cancel(); // someone who should be just a silent observer now interferes with the initialization of the worker


If you wanted to ensure that the worker will be initialized, you'd have to do this:

var worker = fetchDependencies(initializeWorker, err).then();

// elsewhere:
worker.cancel(); // doesn't prevent the worker from being initialized.

/i

Nathan Stott

unread,
Dec 20, 2011, 1:22:47 PM12/20/11
to q-con...@googlegroups.com
What I have been proposing is that the cancel should be a promise to not call the observers registered for the specific agent that called the cancel.  I would propose that in the way I am thinking about it the example you give would not cancel anything unless the promise provider chose as an implementation detail to do so.

Kris Kowal

unread,
Dec 20, 2011, 1:23:18 PM12/20/11
to Mark S. Miller, q-con...@googlegroups.com, Tyler Close
On Tue, Dec 20, 2011 at 9:28 AM, Mark S. Miller <eri...@google.com> wrote:
> A key
> difference is that its extension points, Q.makeFar() and Q.makeRemote(),
> distinguish making a resolved far reference from an unresolved remote
> reference, which seems necessary to enforce the state diagram at
> <http://wiki.ecmascript.org/doku.php?id=strawman:concurrency#promises_and_promise_states>.
> I have yet to understand the security implications of the lack of this
> distinction in the other libraries.

For what it’s worth, Q-Comm is overdue for a rewrite and I failed to
make this distinction because I did not know how to make it work. I’ll
make a study of makeQ and see what I can do on the next iteration. On
a cursory examination, the internal mechanisms do not have obvious
analogs, so it might take some time.

> As an example of the power of the Q approach (whether makeQ.js, ref_send, or
> qcomm), building on makeQ, at
> <http://code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/makeSimpleAMDLoader.js#98>
> I implement a subset of the AMD module loader API in almost no code at all.
> On an adequately ES5 conformant browser, if you visit
> <http://google-caja.googlecode.com/svn/trunk/src/com/google/caja/ses/explicit.html>
> and the last line says "AMD loader test...succeeded" then the example above
> seems to work. However, the testing to date has been very light. Are there
> any extensive Q test suites I might be able to adapt?

Irakli Gozashvili made extensive CommonJS tests for Q. Like all test
suites, some adaptation will likely be necessary both of the scaffold
and of the validity of the tests given our modest divergence.

> I would like to better understand the differences between makeQ, ref_send,
> qcomm, bcap <https://sites.google.com/site/belayresearchproject/bcap>, and
> caja-captp <http://code.google.com/p/caja-captp/>. I would like see makeQ
> and Dr.SES eventually able to interoperate with more of these. Even better
> would be to converge more of the advantages of these various systems into
> one library.

For sure. I can try to write up a list of divergences while I read
makeQ. One is that my Q.all accepts an array instead of being
variadic. We are also rolling with thenables because that property
allows all of our libraries to adapt to each other. Even jQuery, which
has so little in common with our implementations, can still be adapted
because it is compatible with the minimum thenable contract. For what
it’s worth, my Q promises are also whenable to attempt to retain
compatibility with the concurrency strawman.

We also have news. Nathan Stott just posted a new article on promises,
highlighting the interoperability between my fork of Q and Kris Zyp’s
PromisedIO, which I believe is also Dojo’s Deferred.

http://howtonode.org/promises

> As Kris indicates, in makeQ I am taking a very principled stance on
> information flow, as Tyler does in ref_send. Later I hope to find the time
> to address the concerns with cancellation which have been raised in this
> thread. My belief, yet to be demonstrated, is that these concerns can be
> addressed well with simple patterns involving multiple promises.

I feel more and more like I’m going back and forth between shoving a
square block into a round hole and a round block into a square hole.
We need to either find a safe way to make the existing patterns
cancelable or find a way to make pairing cancelers and promises
somehow beautiful enough that it is suitable to use such pairs
everywhere promises are bought and sold.

In a similar vein, Dojo and jQuery the stance that a deferred
conflates a promise and a resolver, which breaks the principle of
least authority and one-way information flow so desperately preserved
in makeQ and ref_send (which I’ve striven for API compatibility with
in Q). They both however provide means to separate the promise and
resolver if at any point you explicitly distrust your consumer (which
is an anti-pattern in the security business; secure should be
default). I am sure it would be suspicious to apply the same
anti-pattern to solve another problem, but we could implicitly
conflate cancellation with a corresponding promise and then explicitly
separate the two with a function call, perhaps with a .nocancel()
call, which would return an object of the same form but a noop
.cancel(). If we did this, it would become the responsibility of every
promise provider to explicitly decide whether cancellation will be
possible and how it would propagate whenever they return a promise to
another consumer.

Kris Kowal

Kris Kowal

unread,
Dec 20, 2011, 1:26:10 PM12/20/11
to q-con...@googlegroups.com
On Tue, Dec 20, 2011 at 10:14 AM, Nathan Stott <nrs...@gmail.com> wrote:
> Mark, does your stance about the flow of information derive from promise
> theory or does it derive from the JavaScript concurrency model being a
> shared-nothing model and wishing to encourage that?

For the record, when Nathan mentions “promise theory”, he is talking
about it in the context of game theory applied to creating stable
policies among selfish autonomous agents in a distributed system. He
sent me this paper yesterday.

http://project.iu.hio.no/papers/pcm.2.pdf

Kris

Nathan Stott

unread,
Dec 20, 2011, 1:35:53 PM12/20/11
to q-con...@googlegroups.com
To restate my thoughts on cancellation more concisely:

No agent should be able to oblige the cancellation of an asynchronous operation that it has received a promise for.  In promise theory, the proper way for command and control relationships to occur is via promises.  Any 'cancel' method should return a promise as a promise from an engineering perspective is not an order but rather something that, if it faults, can be resolved locally by all agents observing that promise.  

For engineering purposes a call to a `then` method of a thenable should be interpreted as an agent attaching observers to a promise.  A call to `cancel` on the returned promise should signal that the agent no longer cares about having those observers invoked.  This does not imply that other agents do not care about their observers being invoked therefore actual cancellation of the asynchronous operation must be an implementation detail based upon reference counting or other heuristics of the promise provider.

Kris Kowal

unread,
Dec 20, 2011, 1:47:36 PM12/20/11
to q-con...@googlegroups.com
On Tue, Dec 20, 2011 at 10:35 AM, Nathan Stott <nrs...@gmail.com> wrote:
> To restate my thoughts on cancellation more concisely:

It is not necessary for cancel to return a promise in order for the
provider to not be obliged to obey. They can already observe the
outcome by watching the resolution of the promise. It may be nice to
disable the original promise and take over with a returned
cancellation promise.

Consider: all promises have a .cancel method that by default returns a
rejected promise. If someone wants to explicitly handle cancellation,
including propagation of cancellation, they must call
.cancellable(handler) on the promise they provide. My previous
example of a memoized function remains correct with no information
backflow, but it can be revised to add the cancellation feature;

var foos = {};
var refcounts = {};


function foo(a) {
if (!foos[a]) {

refcounts[a] = 1;
foos[a] = fooActual(a);
} else {
refcounts[a] += 1;
}
return foos[a].cancelable(function () {
refcounts[a] -= 1;
if (refcounts[a] == 0) {
foo[a].cancel(); // propagate back, probably a no-op
}
});
}

Domenic Denicola

unread,
Dec 20, 2011, 1:55:52 PM12/20/11
to q-con...@googlegroups.com, q-con...@googlegroups.com
Are we sure cancellation should translate to a rejection? To me it a cancelled promise should have neither of its handlers called.

But maybe that's the difference between a cancellation and an abandonment.

Kris Kowal

unread,
Dec 20, 2011, 2:02:12 PM12/20/11
to q-con...@googlegroups.com

That’s in the air. I am presently analyzing the case of memoization in
the context of inadvertent information flow. To prevent a leak, it
would at least be necessary for cancelation to always be treated as a
success. That means that the fulfillment handler must never be called.
If the rejection handler is called, it must be called without delay,
so that the canceller cannot observe whether there were other
subscribers.

That is what is necessary. It would certainly be sufficient for
.cancel() to return undefined and prevent the promise from calling any
further handlers, regardless of whether the cancelation were a
success. Thus, leaving the caller of cancel with the responsibility of
tying up any loose ends.

I am steering in that direction.

Kris Kowal

Mark S. Miller

unread,
Dec 20, 2011, 2:17:26 PM12/20/11
to q-con...@googlegroups.com
Thanks. I was about to say "What promise theory? The design is based on POLA -- the principle of least authority."

But I was unaware of this paper. Looks interesting and relevant. I'll comment again after I've taken a better look at it.
--
    Cheers,
    --MarkM

André Cruz

unread,
Sep 29, 2013, 2:11:51 PM9/29/13
to q-con...@googlegroups.com
I've came across this discussion when finding a similar feature.

When we are building UI there are a lot of cases in which you want to unsubscribe from the promise handlers. For instance, if a section fetches some data but the user navigates to another section, you no longer are interested in that data (except if you are implementing some sort of cache for that data).

I've implemented this functionality directly in the Q library:

define(['q'], function (Q) {
    'use strict';
    // Adds a .release method on the promise that basically
    Q.makePromise.prototype.releasable = function () {
        var deferred = Q.defer();
        this.then(function (value) {
            if (!deferred.promise._released) {
                deferred.resolve(value);
            }
        }, function (reason) {
            if (!deferred.promise._released) {
                deferred.reject(reason);
            }
        }, function (progress) {
            if (!deferred.promise._released) {
                deferred.notify(progress);
            }
        });
        deferred.promise._releasable = true;
        return deferred.promise;
    };
    Q.makePromise.prototype.release = function () {
        if (!this._releasable) {
            throw new Error('Promise is not releasable');
        }
        this._released = true;
        return this;
    };
    return Q;
});

I use it like so:

            this._promise = sdk.getSomething().releasable()
            this._promise.then(function () {
                that._content.render({ dsView: dsView });
            })
            .done();
            // When I'm no longer interested:
            this._promise && this._promise.release()

Hope this helps someone. Thoughts?

Jason Crawford

unread,
Sep 29, 2013, 2:37:07 PM9/29/13
to q-con...@googlegroups.com
I see the motivation for this, but the implementation here gives mutable state to the promise, which could be problematic. For instance, what if two different parts of the code are holding the same promise, waiting for it to be settled, and one of them cancels? If I'm reading your implementation right, the promise would never settle, and the other part of the code, which didn't “release” the promise and didn't know it was released, would end up waiting for it indefinitely.

In the context of an event-driven UI, a model like reactive programming may be better suited to the task. You might look into a library like Bacon.js.

I'm interested in this topic too, so I'd love to hear someone more knowledgeable speak up.

-Jason

-- 
Blog: http://blog.jasoncrawford.org  |  Twitter: @jasoncrawford



--
You received this message because you are subscribed to the Google Groups "Q Continuum (JavaScript)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to q-continuum...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Gorgi Kosev

unread,
Dec 10, 2013, 8:38:08 AM12/10/13
to q-con...@googlegroups.com
Perhaps this "promise switch" pattern can be used?


Onur Yıldırım

unread,
Apr 12, 2014, 8:09:53 PM4/12/14
to q-con...@googlegroups.com
I think the observers should *vote* for a cancellation. And when the producer sees that the vote count is eligible (and each vote is genuine and unique) — cancels the promise.

IMO, cancellation is definitely not a rejection. Rejection is actually done by the producer when things go off the rails. You would mostly cancel because you're already "fulfilled".

Kris Kowal

unread,
Apr 12, 2014, 9:13:36 PM4/12/14
to Q Continuum
It is certainly possible to do, if not “voting” in name or spirit, retaining reference counting, but it requires breaking the “sharable by default” property of promises. We would have to go to a “sharable if explicitly annotated” model, which comes with safety hazards.

The utility of cancelable promises is certainly no contest. There are many cases where we want to be able to stop doing large workflows when their results are invalidated and the resources that those workflows consume should be released.

Having a rejection propagate through the promise chain would be quite important in ensuring that any resources retained by the workflow have been release, e.g., sockets closed, scheduled work on the file system aborted.

I am tempted to entertain an experiment to implement reference counting cancellability, available by default, in the Q v2 experimental/unstable branch.
Reply all
Reply to author
Forward
0 new messages