[strawman] Symbol.thenable proposal

Jordan Harband ljharb at gmail.com
Mon Apr 16 20:34:58 UTC 2018

I think the purpose of this proposal is to *forbid* module authors from
making their Module Record namespace object thenable, since doing that
causes confusion.

Setting a property on the function doesn't work when the "then" is
inherited but the object still wants to be non-thenable; I think that the
property has to go on the object - the thing that's not thenable - not on
the "then" function itself, which wouldn't even have to be touched or
looked at if the object is non-thenable.

On Mon, Apr 16, 2018 at 1:15 PM, Ben Newman <benjamin at cs.stanford.edu>

> I agree with Tab that allowing developers to opt out of thenable behavior
> is the only recourse now, but I don't think `Symbol.thenable` is the best
> way to grant that power.
> In the case of module namespaces, since module authors cannot use
> `Symbol.thenable` as the name of an export, there's no way to export a
> `then` function while also opting out of thenable behavior by _exporting_
> `Symbol.thenable` as `false`.
> Instead, this proposal seems to imply that `Symbol.thenable` would have to
> be added automatically to every module namespace object, which could be bad
> news for module authors who (for whatever reason) actually wanted to export
> a `then` function to make the namespace thenable.
> In general, whether or not we're talking about module namespace objects,
> we're beginning to realize that the presence of a function-valued `.then`
> property is not enough information to decide thenability in all cases,
> though of course it must remain the default behavior because that's how
> promise libraries have been written, and folks still do use promise
> libraries instead of the native `Promise` (e.g. Bluebird).
> If an object does not have a `.then` function, then clearly it should not
> be treated as thenable, so I wonder if there might be a way to disambiguate
> individual `then` functions according to developer intent. Specifically,
> what if the developer could set `object.then.able = false` to prevent
> `object` from being treated as thenable, even though it has a
> function-valued `then` property? In addition to checking for the presence
> of a `then` method, `Promise` implementations would also need to check that
> `then.able !== false` before treating the object as thenable. If the
> `.able` property is absent from the `then` function (or truthy), the normal
> thenability behavior would continue to apply.
> This proposal addresses the module namespace problem I described above,
> since you can set `then.able = false` before exporting `then` from a
> module. Note: because of function hoisting, it might be possible to get
> access to the `then` function before `then.able` has been set, but I don't
> think that's a problem for dynamic `import()`, since the module has to
> finish evaluating before the `import()` promise resolves.
> While I think `then.able` is kinda cute (which is worth… something?), I
> would also be totally open to alternatives like setting
> `then[Symbol.thenable] = false`. The essential idea is to use properties of
> the `then` function itself (rather than sibling properties of the object)
> to indicate whether the object should be thenable.
> Ben
> His errors are volitional and are the portals of discovery.
> -- James Joyce
> On Mon, Apr 16, 2018 at 1:36 PM, Tab Atkins Jr. <jackalmage at gmail.com>
> wrote:
>> On Fri, Apr 13, 2018 at 6:00 PM, Isiah Meadows <isiahmeadows at gmail.com>
>> wrote:
>> > I can't remember where, but I recall seeing this discussed elsewhere
>> > (maybe in the TC39 meeting notes?) and the conclusion was basically
>> > ¯\_(ツ)_/¯. I'm not convinced myself it's actually worth the extra
>> > symbol just to make something not considered a thenable - all these
>> > Promise libraries have been able to get away with it for this long;
>> > what makes ES promises any different here? (Dynamic import is probably
>> > the only possible case I can think of short certain proxies in terms
>> > of things that could be considered thenables but shouldn't always.)
>> Having a reserved property key is a big footgun; you *can't* reliably
>> resolve a promise to an arbitrary object, because if the object
>> happens to have a "then" key, it'll try to recursively resolve it.
>> Userland may "get away with it" because "then" isn't a common key, but
>> it's still a hassle, same as how __proto__ being a special key in JS
>> causes problems with JSON usage - it's rare, but not unknown, and
>> problematic because it's an obvious layering violation.
>> > Worst case, you can just return a value that happens to have a promise
>> > in a property, like in `{value: somePromise}` - nobody resolves that
>> > except `co` IIRC.
>> Note that this isn't about nesting promises (or even promise-likes)
>> without recursive resolution, it's about nesting *thenables* into a
>> promise without (attempted) recursive resolution, which is a much
>> larger conceptual class: the presence of a "then" property says
>> absolutely nothing about the type of an object. We've just been making
>> a statistical judgement about the posterior probability of an object
>> being promise-like based on the presence of a particular key, which is
>> very dubious.
>> I'm still strongly of the opinion that we messed this up; the reason
>> we went with thenables was because it's how userland did it, and they
>> were working under the constraints of a proliferation of promise-likes
>> and the difficulty of userland type-testing; real Promise usage
>> promulgated much quicker than the pessimistic estimates, tho, making
>> the relative trade-off of "free" compatibility with promise-likes vs
>> the layering violation of reserving a property name on every object
>> forever much less favorable.  We should have instead relied on the
>> actual Promise type, with a Symbol escape-hatch opting a userland
>> object into being a promise-like.  Doing the reverse and letting
>> objects opt *out* of being treated as promise-like is probably the
>> most we can do at this point, unfortunately. Generic data-handling
>> will just have to manually add such a Symbol to objects they're
>> resolving to, which sucks but is better than the alternative of always
>> using a wrapper object.
>> (Note that having a "strictResolve" method won't help; it might
>> prevent the precise promise you construct from recursing into the
>> object, but if you then resolve *another* promise to that promise,
>> it'll go ahead and try to recurse again.  Maybe strictResolve() could
>> auto-add the "I'm not a promise-like" symbol to the object it resolves
>> to? That would be a bit more ergonomic for generic handling.)
>> ~TJ
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180416/2dcd065d/attachment.html>

More information about the es-discuss mailing list