const {resolve} = Promise; // fails
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.
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.
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
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 ?
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.
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);
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?
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.
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.
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.
This question has been answered here: tc39/ecma262#544
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.
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.
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?
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.
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]
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
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 .