[strawman] Symbol.thenable proposal

Ben Newman benjamin at cs.stanford.edu
Mon Apr 16 20:15:35 UTC 2018

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.


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>

> 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180416/c26e38c6/attachment-0001.html>

More information about the es-discuss mailing list