Don't test promise results on their thenability multiple times

a.d.bergi at a.d.bergi at
Thu Apr 16 10:46:36 UTC 2015

while working on the promise memory-leak problem <> I noticed the following in the current ES6 spec:

var count = 0,
    nonThenable = {get then() { count++; return undefined; }};

function GetResult(n) {
    if (n <= 0)
        return nonThenable;
    return Promise.resolve().then(()=> // just for a little delay
GetResult(5).then((res) => {
    assert(res == nonThenable);
    assert(count == 1); // !!!
Since we only *resolved* a promise once with `nonThenable` (the promise that is returned from `GetResult(1)`), I expect that `.then` is only accessed once. The other 4 promises are *fulfilled* with it, and we know that [[PromiseResult]]s are never thenables. Or should not be, if it didn't change in the meantime.

I suggest to change the spec so that a Promise Resolve Function ( is not called multiple times, even if multiple promises (that adopt each other) are fulfilled with it. This would pave the road to allow fixing memory leaks from long recursive adoption chains, so that "intermediate" promises are no longer considered and can be garbage-collected before the eventual resolution.

A possible change could be made by amending the sections CreateResolvingFunctions and Promise.prototype.then like so:

* A promise resolve function is an anonymous built-in function that has [[Promise]], [[AlreadyResolved]] *and [[fulfillAdopter]]* internal slots.

* CreateResolvingFunctions would set the [[fulfillAdopter]] slot of `resolve` to a function like
      function fulfiller(resul) {
          if (! F.[[AlreadyResolved]]) {
              F.[[AlreadyResolve]] = true
              Return FulfillPromise(F.[[Promise]], result)

* Insert a step 5.5 in PerformPromiseThen, like
  If `onFulfilled` has a [[fulfillAdopter]] internal slot, set fulfillReaction.[[handler]] to onFulfilled.[[fulfillAdopter]]

So when a promise is adopted, and its `.then()` method is called, a `fulfill` handler is installed not a `resolve` handler. Which works as long as the invariant holds that result values are not thenable.

I haven't thought through the implications that this had on subclassing, but this is supposed to work even if `.then` was overwritten as long as it calls `super.then()` with the original resolve handler. (If it wrapped the resolve handler, we should resolve the value again indeed).


More information about the es-discuss mailing list