slightlyoff / Promises

DOM Promises IDL/polyfill
Apache License 2.0
154 stars 28 forks source link

Let's resolver return a dispose function #62

Closed nfroidure closed 8 years ago

nfroidure commented 11 years ago

While implementing Promises i had some problems on making resources eligible for garbage collection. The typical use cas is the Promise.some method. Let's consider this code :

var p= Promise.some(
    new EventPromise(document,'click'),
    new EventPromise(document,'keypress'),
    new EventPromise(document,'touch')
).then(function() {
    // start my app
});

Internally, the EventPromises are registering callbacks to the DOM document. So, it's callbacks will stay in memory during all the application life cycle. Knowing that the promises are unique operations, it could lead to important memory consumption for applications. XHRPromises could lead to very important memory leaks cause they often retrieves a huge amount of datas.

That's why i think we should let the resolver function return a "disposer" function that would let the Promise.some method dispose cancelled Promises.

The main advantage of this method is it keeps backward compatibility while it allows the dispose function to be propagated througth promises trees.

It implies 2 things :

Thanks for reading.

slightlyoff commented 11 years ago

I think I understand what's being proposed in terms of the behavior of dispose(), but not the implications you raise regarding promise callbacks being synchronous. The core contract of Promises is that they are never apparently synchronous in order to prevent a class of common bug that manifests when surrounding code creates implicit dependencies and/or uses side-effects of promise delivery.

The registration order delivery is interesting. I think, today, that the behavior of the polyfill is to deliver in registration order, but I'm not sure it's spec'd that way.

@annevk would be able to say for sure.

annevk commented 11 years ago

The specification will change somewhat with how it integrates with the event loop.

nfroidure commented 11 years ago

@slightlyoff in fact, i think i've explained the behavior in a bad way (my english needs improvement).

By "keeping promise callbacks synchronous" i wanted to say that when the success() function is called inside the Promise resolver, the dispose function should be called in the same tick to prevent the fullfill of any other promise between the success of a promise and the dispose of the promises that depends on this success.

That's what i tried to illustrate here : https://github.com/nfroidure/Promise/blob/master/tests/index.html

In this example, i create a promise "d" that fullfill a second later, then i use the d promise in a Promise.any composition :

var d=Promise.elapsed(1000);
d.then(function(){
    console.log('Then 1 : First executed');
});
var tree=Promise.any(
    d.then(function(){
        console.log('Then 2 : Executed then');
    }),
    Promise.all(Promise.sure(),d.then(function(){
        console.log('Then 3 : Never executed');
    })),
    Promise.dumb()
).then(function() {
    console.log('done');
});

When the d promise fullfills, it begins executing its associated callbacks (in the same order they have been set to enable developers to predict their execution). When the first callback associated to d by Promise.any executes (Then 2), each other promises set to Promise.any are cancelled so that the third callback associated to d (Then 3), is never called.

As you can see, be able to dispose promises not only allow us to avoid memory leaks, but also to have a better control on how our promise trees will be executed.

For a real world use case, take a look at this small game based on a giant promise tree : https://github.com/nfroidure/Liar

I've been able to reduce the game flow by only using promises, i never could without a dispose function, i would have lead to so much leaks. Here is a piece of the promise tree to let you understand the principle. Each view is a promise and loop on it's own Promise three until it exits :

HomeViewPromise --> MenuCommandPromise |--> OptionsViewPromise | any |--> OptionsFormSubmissionPromise - success |--> BackButtonPromise - success |--> RoomsViewPromise | all |--> RoomsListXHRPromise |--> UserProfilePromise | (...) | then, any |--> RefreshRoomsPromise |--> EnterRoomPromise |--> BackButtonPromise - success |--> AboutViewPromise | any |--> BackButtonPromise - success

It's still a bit experimental, i wish i've been clear enough, let me know otherwise.