Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 404306

Summary: Deferred then() throws when called as an unbound method
Product: [ECD] Orion Reporter: Mark Macdonald <mamacdon>
Component: ClientAssignee: Simon Kaegi <simon_kaegi>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: grant_gayed, simon_kaegi
Version: 2.0   
Target Milestone: 3.0 M1   
Hardware: PC   
OS: Windows 7   
Whiteboard:

Description Mark Macdonald CLA 2013-03-25 15:57:17 EDT
While investigating an update of GCLI, Grant found that the Orion Deferred doesn't work with the new GCLI. You can reproduce this as follows:
1. Upgrade the GCLI code in bundles/org.eclipse.orion.web.ui/web/gcli to the latest version of GCLI
2. Launch the Orion Node server
3. Go to the shell page and run any command that returns a Deferred. For example: "node start my_app.js"
4. A TypeError is thrown by the Orion Deferred. The command fails.

The reason is this GCLI code, which wraps the "then()" method from our Orion Deferred, and calls it without the dot operator:

> Requisition.prototype._then = function(thing, onDone, onError) {
>   var then = null;
>   if (thing != null && typeof thing.then === 'function') {
>     // Simple promises with a then function
>     then = thing.then;
>   }
>   ...
> 
>   if (then != null) {
>     then(onDone, onError); // XXX trouble
>   }
>   else {
>     onDone(thing);
>   }
> };

When the "then" method of Orion's Deferred is invoked without a receiver object, it blows up. The problematic code is:

> // Note: "then" ALWAYS returns before having onResolve or onReject called as per http://promises-aplus.github.com/promises-spec/
> this.then = function(onResolve, onReject, onProgress) {
>    ...
> 	var deferred = listener.deferred;
> 	var thisCancel = this.cancel.bind(this); // XXX fail here
Comment 1 Mark Macdonald CLA 2013-03-25 16:13:50 EDT
Grant's fix is to wrap Orion Deferred's implementation of this() with .bind, so it always receives the proper context. However, that causes one of the unit tests dealing with cancellation to fail.
Comment 2 Mark Macdonald CLA 2013-03-25 18:34:45 EDT
http://git.eclipse.org/c/orion/org.eclipse.orion.client.git/commit/?id=79fe18727b49d8186a54e9474df4293c51478a5f

^^ We might have to revisit this given the test failure. 

I guess we could make the value of "thisCancel" be different depending on whether then() was being called on a promise or not. But I really have no idea what the intended semantics are here. :(
Comment 3 Simon Kaegi CLA 2013-04-02 12:25:32 EDT
Being able to call "then" as a pure function is not strictly part of Promises A+ but still worthwhile.

For semantics...
A user might customize the "cancel" method of a promise so we need to still honor that customization when "then" is called as a function instead of as a method.
Comment 4 Simon Kaegi CLA 2013-04-02 12:30:39 EDT
I've changed the use of "this" in "then" to instead use "_this". "promise.cancel" now uses getter and setters to ensure Deferred.cancel === promise.cancel