const {resolve} = Promise; // fails

# Andrea Giammarchi (6 years ago)

As quickly discussed on Twitter, it's very inconvenient and inconsistent that the following fails:

const {resolve, reject} = Promise;

resolve(123); // throws

Compared to every other public static method in ECMAScript that works, including those methods that might need the contextual class, as it is for the Array.from case.

const {from} = Array;

from({0: 'abc', length: 1}); // ["abc"] // all good

Why cannot Promise methods fallback to Promise constructor when the class/context is not available?

Wouldn't be simple/reasonable change to make so that developers expectations would be preserved?

Best .

# Michael Luder-Rosefield (6 years ago)

I'd reinforce this with the fact that this works for RSVP.js tildeio/rsvp.js, and so the current behaviour is a potential breaking point if code is being converted to use native Promises.

# Claude Pache (6 years ago)

Le 19 juil. 2018 à 13:56, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :

Compared to every other public static method in ECMAScript that works, including those methods that might need the contextual class, as it is for the Array.from case.

const {from} = Array;

from({0: 'abc', length: 1}); // ["abc"] // all good

That pattern falls apart when subclassing:

class ImprovedArray extends Array {
	/* implementation NOT overriding the `from` static method  */
}

const {from} = ImprovedArray

from({0: 'abc', length: 1}) // Array instance instead of ImprovedArray instance

So, no, the Array.from precedent is a bad one.

# T.J. Crowder (6 years ago)

On Thu, Jul 19, 2018 at 12:56 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

Why cannot Promise methods fallback to Promise constructor when the class/context is not available?

That sounds reasonable on first glance, but I'd be concerned about what happens when you do it after subclassing:

class MyPromise extends Promise {
    // ...and adds some important feature...
}
// ...
const {resolve, reject} = MyPromise;
const p = resolve();
p.someImportantFeature(/*...*/); // TypeError: undefined is not a function

...since resolve fell back to Promise. That feels like a footgun. Either subclassers would have to handle that, which they will forget to do, or it has to be a bit more complicated than just a simple fallback to Promise (I don't immediately know what that "more complicated" answer would be.)

-- T.J. Crowder

# Andrea Giammarchi (6 years ago)

I know it's about subclassing, which is why I've asked why, once there's no context, the default/base one is not considered, but since everyone came back with the subclassing issue, which is actually what I've said myself on twitter about the current state, how about changing all public static methods that need it, to be getters ?

class Promise {
  #resolve(...args) {
    return this.nativeImplementation(...args);
  }
  get resolve() {
    return #resolve.bind(this);
  }
}

we could argue Promise.resolve === Promise.resolve should be preserved, as behavior, so that we need a lazy defined getter ... but why not making public static restructuring from known constructors work regardless, under all circumstances ?

# Andrea Giammarchi (6 years ago)

sorry, that'd be public get resolve() and also public #resolve() so nobody should be confused about the fact I'm talking about public static methods.

# Andrea Giammarchi (6 years ago)

I guess one example would be more explicative: why cannot public static methods be defined in a similar manner?

const withLazyBoundObjects = new WeakMap;
const withLazyBoundMethods = obj => {
  const descriptors = Object.getOwnPropertyDescriptors(obj);
  Object.keys(descriptors).forEach(key => {
    const desc = descriptors[key];
    const {value} = desc;
    if (desc.configurable && typeof value === 'function') {
      delete desc.value;
      delete desc.writable;
      desc.get = function (...args) {
        let methods = withLazyBoundObjects.get(this || obj);
        if (!methods)
          withLazyBoundObjects.set(this, methods = Object.create(null));
        return methods[key] || (methods[key] = value.bind(this));
      };
    }
  });
  return Object.defineProperties(obj, descriptors);
};

// example
const {resolve, reject} = withLazyBoundMethods(Promise);
resolve(123).then(console.log);
# Michael Luder-Rosefield (6 years ago)

At this point I can't ignore how much overlap there is between this and the this-binding operator proposal tc39/proposal-bind-operator

const resolve = ::Promise.resolve; // takes and binds

As someone who often extracts functions with deconstruction, I'd love for there to be an extension to this proposal to handle this case.

const { resolve, reject } = ::Promise; // ?

That would leave the question though of what to do with nesting:

const { fn1, foo: { fn2 } } = ::bar; // fn1 is bound to bar. Is fn2 bound
to bar, or foo?
# Claude Pache (6 years ago)

Le 19 juil. 2018 à 16:32, Andrea Giammarchi <andrea.giammarchi at gmail.com> a écrit :

I know it's about subclassing, which is why I've asked why, once there's no context, the default/base one is not considered, but since everyone came back with the subclassing issue, which is actually what I've said myself on twitter about the current state, how about changing all public static methods that need it, to be getters ?

class Promise {
  #resolve(...args) {
    return this.nativeImplementation(...args);
  }
  get resolve() {
    return #resolve.bind(this);
  }
}

we could argue Promise.resolve === Promise.resolve should be preserved, as behavior, so that we need a lazy defined getter ... but why not making public static restructuring from known constructors work regardless, under all circumstances ?

Nice hack... But it imposes all subclasses of Promise that override the resolve method to use a similar trick, because the following will break:

class MyPromise extends Promise {
    static resolve() {
        // do fancy stuff
        return super.resolve()
    }
}

const {resolve} = MyPromise

resolve() // TypeError: undefined is not an object

Per the KISS principle, let’s avoid to be clever.

# Andrea Giammarchi (6 years ago)

Yes Michael, the :: operator is the best thing ever proposed and got killed / pseudo replaced by another, completely different, operator that won't solve problems :: would: the |> pipe one.

I'm a big fan of the :: operator, and if it were there, I would quite possibly also like the "per context" restructuring.

# Andrea Giammarchi (6 years ago)

Per the KISS principle, let’s avoid to be clever.

I think my next code example is less clever, but the only reason I've written hacks or code was not to be advocated or adopted, just to explain what could happen internally.

TL;DR why aren't public static methods that need context all lazily defined as bound (once) on demand? That would make every single public static method consistent, accordingly with the Class you extracted them from, right? That's not clever, that's usually developers expectations because historically all public static methods don't need the Class to work.

# Jordan Harband (6 years ago)

This question has been answered here: tc39/ecma262#544

# Andrea Giammarchi (6 years ago)

Reading that looks like nobody answered my question, explained through some code that wouldn't have any issue with subclassing.

So no, there's no answer to my latest question in there, unless I've missed it.

# Jordan Harband (6 years ago)

TL;DR why aren't public static methods that need context all lazily

defined as bound (once) on demand? That would make every single public static method consistent, accordingly with the Class you extracted them from, right? That's not clever, that's usually developers expectations because historically all public static methods don't need the Class to work.

I believe tc39/ecma262#544 answers this - specifically, expressing that the claimed better design is for nothing to be bound.

# Andrea Giammarchi (6 years ago)

My code doesn't suffer what Domenic says.

Once again:

const withLazyBoundObjects = new WeakMap;
const withLazyBoundMethods = obj => {
  const descriptors = Object.getOwnPropertyDescriptors(obj);
  Object.keys(descriptors).forEach(key => {
    const desc = descriptors[key];
    const {value} = desc;
    if (desc.configurable && typeof value === 'function') {
      delete desc.value;
      delete desc.writable;
      desc.get = function (...args) {
        let methods = withLazyBoundObjects.get(this || obj);
        if (!methods)
          withLazyBoundObjects.set(this, methods = Object.create(null));
        return methods[key] || (methods[key] = value.bind(this));
      };
    }
  });
  return Object.defineProperties(obj, descriptors);
};

// patch the Promise
withLazyBoundMethods(Promise);

// have a class that extends Promise
class SubPromise extends Promise {}

// test inheritance
SubPromise.resolve() instanceof SubPromise; // true
(0,SubPromise.resolve)() instanceof SubPromise; // true

// even the Promise ?
Promise.resolve() instanceof SubPromise; // false, it's Promise
(0,Promise.resolve)() instanceof SubPromise; // false, it's Promise

So, why cannot we have above behavior in core?

# Jordan Harband (6 years ago)

That's certainly a clever solution, but one without precedent in the language. It also means you can't Promise.resolve.call(SubPromise), although perhaps you could avoid that by skipping .bind and creating a normal function wrapper inside the getter - I'm curious how that might be implemented as well as specified.

# Andrea Giammarchi (6 years ago)

one without precedent in the language

that's how languages move forward, right?

It also means you can't Promise.resolve.call(SubPromise)

you can make it more complex and yet have a fallback to the invoker, not a real issue, just more rules to write down.

const withLazyBoundObjects = new WeakMap;
const withLazyBoundMethods = obj => {
  const descriptors = Object.getOwnPropertyDescriptors(obj);
  Object.keys(descriptors).forEach(key => {
    const desc = descriptors[key];
    const {value} = desc;
    if (desc.configurable && typeof value === 'function') {
      delete desc.value;
      delete desc.writable;
      desc.get = function () {"use strict";
        const self = this || obj;
        let methods = withLazyBoundObjects.get(self);
        if (!methods)
          withLazyBoundObjects.set(self, methods = Object.create(null));
        return methods[key] || (methods[key] = function () {
          return value.apply(this || self, arguments);
        });
      };
    }
  });
  return Object.defineProperties(obj, descriptors);
};

withLazyBoundMethods(Promise);

class SubPromise extends Promise {}

console.log([
  (0,Promise.resolve)() instanceof SubPromise,
  Promise.resolve() instanceof SubPromise,
  (0,SubPromise.resolve)() instanceof SubPromise,
  SubPromise.resolve() instanceof SubPromise,
  Promise.resolve.call(SubPromise) instanceof SubPromise
]);

// [false, false, true, true, true]
# Andrea Giammarchi (6 years ago)

FYI, in case anyone is interested on my user-land solution, there is a (dual) module now, and it's called self-aware [1]

It's a slightly revisited, 100% code covered, generic method "patcher" to fix Promise and/or others.

Best

[1] WebReflection/self-aware#self