00:02 | <bakkot> | ah |
00:02 | <bakkot> | the problem specifically being that Promise.prototype.then does the SpeciesConstructor stuff, I suppose |
00:03 | <bakkot> | otherwise you could just use Promise.prototype.then.call(val) as your test |
00:03 | <Mathieu Hofman> | and Promise.resolve, yes |
00:05 | <Mathieu Hofman> | There is no place in the spec that does IsPromise without also poking at .constructor or .then on the object. |
00:06 | <bakkot> | perhaps we will manage to remove Symbol.species instead |
00:06 | <Mathieu Hofman> | I don't think it'll help. .constructor based species is not slated for removal from what I recall |
00:09 | <bakkot> | that's Type II in the taxonomy in the proposal, and it is proposed to be removed per https://github.com/tc39/proposal-rm-builtin-subclassing#proposed-new-old-semantics |
00:09 | <bakkot> | I don't think we'll actually manage it but I live in hope |
00:10 | <Mathieu Hofman> | I'm curious if there would be any opposition to basically have a Promise.isPromise(x) which basically does IsPromise(x) . I know it'd solve my problem |
00:11 | <bakkot> | I think there would need to be more motivation than your very narrow use case |
00:12 | <bakkot> | and I'd be hard pressed to come up with such motivation given that the actual thing one usually cares about is "is thenable" |
00:13 | <Mathieu Hofman> | The thing is that it's impossible to workaround currently, and I fail to see how exposing something that already exists as a new namespaced API has much complexity. |
00:15 | <Mathieu Hofman> | I am sure ljharb would love a clean way to brand check a promise for his libraries ;) |
00:15 | <bakkot> | People would use it instead of "is thenable" and be confused |
00:16 | <bakkot> | "is promise" is almost never the test anyone actually wants; you and ljharb have extremely unusual use cases |
00:16 | <bakkot> | but it looks like a thing you might want, so people will use it in other cases, and that's bad |
00:19 | <Mathieu Hofman> | Do you have any suggestion to make it more obvious what this does, or less likely to be misused? |
00:19 | <Kris Kowal> | I’m sure there’s a way to express the brand-check that would be clear and sufficiently out of reach. |
00:21 | <Kris Kowal> | I agree that there’s potential for confusion for any particular behavior for Promise.isPromise(thenable) . |
00:22 | <Kris Kowal> | And I also agree we need to be able to check whether a value is a native promise without reentering any of its API. |
00:24 | <Kris Kowal> | Though, Promise.isPromise(thenable) === false isn’t that weird of an answer. It’s not a Promise yet, even if it’s a promise with a little P. |
00:26 | <Kris Kowal> | The notion that Promise.isPromise(X) === true only for X that can become a promise invites the possibility that "hello" is a promise. |
00:35 | <bakkot> | IME the main reason people use isArray is to make polymorphic APIs, where e.g. you take either a list of names or a single name. and I expect that's the main way people would use this as well: if (Promise.isPromise(x)) x = await x , say. but that doesn't do the right thing if x is thenable but not a Promise. |
00:36 | <bakkot> | this seems bad. especially for people who want to make proxies for stuff unless we make isPromise pierce proxies like isArray does, which we definitely will not do |
00:36 | <ljharb> | in practice tho it’s rarely a thenable |
00:36 | <Mathieu Hofman> | I would not expect proxies to pierce this |
00:36 | <Kris Kowal> | The example is not coherent because await 1 works fine, but 1 is not a promise. |
00:36 | <bakkot> | the example is perfectly coherent |
00:36 | <ljharb> | non-Promise thenables largely died once Promise and async/await became commonplace |
00:36 | <bakkot> | ljharb: depends on how many proxies you have around |
00:36 | <bakkot> | proxy for a promise is thenable but not a promise |
00:37 | <ljharb> | “Any use of proxy” is incredibly niche automatically :-) |
00:37 | <bakkot> | fair enough |
00:37 | <bakkot> | Kris Kowal: how is the example not coherent? |
00:37 | <bakkot> | I mean, it's wrong, because it's treating "thenable" as the same as "is promise", but I guarantee people are going to do that; that's my point |
00:38 | <bakkot> | if we didn't have thenables and only had promises, the example would be correct for a polymorphic API which wants to skip the await when passed a value which is not boxed in a promise |
00:38 | <Mathieu Hofman> | I agree that using isPromise with the intent to skip an await would be misguided. |
00:38 | <bakkot> | which is a thing people do in fact want to do |
00:38 | <bakkot> | constantly |
00:38 | <ljharb> | not just polymorphic; these kinds of checks are always useful for helpful runtime validation of input types |
00:39 | <ljharb> | which includes an IsPromise check, which thus isn’t quite as niche as claimed |
00:39 | <Mathieu Hofman> | currently people do Promise.resolve(val) === val , but that can synchronously trigger user code attached to val |
00:40 | <ljharb> | Which is absurdly rare, so it’s ‘good enough’ but still not perfect |
00:40 | <bakkot> | I am fine with this |
00:40 | <Kris Kowal> | I’m drawing a blank on what sensible thing one would be achieving withif (Promise.isPromixe(x)) await x that would not be achieved by await x . |
00:40 | <bakkot> | skipping the await when passed a value which is not boxed in a promise, like i said |
00:40 | <ljharb> | Kris Kowal: also telling someone “i wanted a promise here, and you gave me something else, so you probably have a bug” |
00:41 | <ljharb> | if i expect “an awaitable” then it doesn’t matter, and if i accept a thenable then I’d check for .then being a function ¯_(ツ)_/¯ |
00:41 | <Kris Kowal> | Indeed. |
00:41 | <ljharb> | but virtually nobody expects a thenable - they expect a Promise. |
00:42 | <bakkot> | false |
00:42 | <bakkot> | I have absolutely no problem with someone handing me a proxy for a promise |
00:42 | <Mathieu Hofman> | What if we introduce both a isPromise and isThenable , then people wouldn't be as confused ? |
00:42 | <bakkot> | or a different-realm promise |
00:42 | <bakkot> | those things are fine |
00:42 | <ljharb> | intentionally i mean. A thenable works perfectly fine ofc |
00:42 | <bakkot> | Mathieu Hofman: that would be... more confusing |
00:42 | <ljharb> | and “a different realm” is almost as rare as proxy |
00:43 | <Jessidhia> | is it still common for people to use <favorite A1 implementation> instead of native Promise or a polyfill |
00:43 | <bakkot> | people do still use bluebird pretty often, yes |
00:43 | <bakkot> | not as much as they used to |
00:43 | <ljharb> | mostly by inertia, not for a good reason |
00:43 | <Mathieu Hofman> | Mathieu Hofman: that would be... more confusing |
00:44 | <bakkot> | "thenable" means "has a callable .then property", which is a thing you can check directly; the existence of a method which does this implies it is something else |
00:44 | <Mathieu Hofman> | is it still common for people to use <favorite A1 implementation> instead of native Promise or a polyfill |
00:45 | <ljharb> | weird |
00:45 | <Kris Kowal> | Q’s still getting an absurd weekly download figure, for what it’s worth. |
00:45 | <bakkot> | so, right. bluebird and Q are good examples of why normal users don't actually want IsPromise in the example I gave, and giving them IsPromise would lead them to have the wrong behavior. |
00:45 | <bakkot> | so, we should not have IsPromise. |
00:45 | <ljharb> | you could probably hunt through dependents and figure out how much of those numbers are due to new usage |
00:46 | <ljharb> | normal users don’t use bluebird or q directly, i claim, and thus have no desires for them |
00:46 | <Jessidhia> | avoiding await s seems very misguided IMO, that’s just borderline attempting to revive the Zalgo problem |
00:46 | <bakkot> | I don't know what "thus have no desires for them" is doing in that sentence |
00:46 | <Kris Kowal> | Also, Q.isPromise is real and does a brand check of Q promises. |
00:46 | <ljharb> | (I’m not expecting IsPromise to ever be exposed, ofc) |
00:46 | <bakkot> | you might end up with a bluebird promise because someone else gave you one |
00:46 | <bakkot> | and you shouldn't trip over and die as soon as that happens |
00:47 | <bakkot> | you should just use it like any other promise |
00:47 | <bakkot> | and life will be good |
00:47 | <ljharb> | I don't know what "thus have no desires for them" is doing in that sentence |
00:47 | <bakkot> | yeah but |
00:47 | <bakkot> | they are there |
00:47 | <bakkot> | and they don't have to realize it |
00:47 | <bakkot> | specifically because we don't have IsPromise |
00:47 | <bakkot> | which is... good |
00:47 | <ljharb> | sure. But as an API producer i have the right to force them to realize it if i want. |
00:48 | <ljharb> | and i can do that now already. Just imperfectly. |
00:48 | <bakkot> | I don't know what "have the right" means |
00:48 | <Jessidhia> | I remember that for a good while bluebird had better DX than native; I haven’t revisited this in years though |
00:48 | <bakkot> | you can write whatever code the language allows you to write, yes |
00:48 | <ljharb> | lol i mean, i can do it, and it’s not a bad thing if i do it |
00:48 | <bakkot> | I'm not going to pass judgement on "a bad thing" |
00:49 | <ljharb> | right - so i think while the argument in favor of the predicate is weak, the argument against it is also weak |
00:49 | <bakkot> | my argument has nothing to do with whether API consumers have the right to deliberately reject bluebird promises if they are so inclined |
00:49 | <ljharb> | iow “non-Promise thenables exist” isn’t a good argument imo against IsPromise |
00:49 | <bakkot> | my claim is that, for a normal user, who is doing the totally normal thing of trying to skip an await when given something which is not boxed, IsPromise would lead them to doing the wrong check, because that would lead them to skipping bluebird promises, which is not something they are specifically trying to do |
00:50 | <ljharb> | perhaps. How can we know that unless they learn the difference? |
00:50 | <Mathieu Hofman> | (I’m not expecting IsPromise to ever be exposed, ofc) |
00:50 | <bakkot> | they should not have to learn the difference |
00:50 | <bakkot> | that's my point |
00:50 | <Kris Kowal> | I do agree that we should generally steer folks away from checking whether things are promises. |
00:50 | <bakkot> | the thing I presented them as wanting to do was not "reject bluebird promises" |
00:51 | <ljharb> | i disagree with that opinion; Promise landed and other thenables are thus undesirable |
00:51 | <bakkot> | that is a totally unrelated thing to what I was hypothesizing |
00:51 | <Jessidhia> | my argument against it is an argument against the proposed use of the predicate — conditional await is misguided, leads to unpredictable execution order (“Zalgo 2.0”), and if you’re already in a context where you can await you are already asynchronous and there is no benefit to skipping awaits other than skipping the cooperative microtask yield point |
00:51 | <Kris Kowal> | I also agree that we should discourage conditionally awaiting. |
00:52 | <bakkot> | ljharb: it may well be that other thenables are undesirable but I don't understand what that has to do with the user story I'm discussing |
00:52 | <bakkot> | other thenables exist |
00:52 | <bakkot> | code which does if (typeof arg.then === 'function') arg = await arg; correctly interoperates with those thenables |
00:52 | <bakkot> | code which does if (Promise.isPromise(arg)) arg = await arg would not |
00:52 | <Kris Kowal> | I believe you’re also arguing that folks will inevitably abuse Promise.isPromise(x) in all these ways if a function by that name exists. |
00:52 | <bakkot> | yeah |
00:53 | <bakkot> | which is a claim I stand by |
00:53 | <devsnek> | i can see the dev.to articles now |
00:53 | <Kris Kowal> | I’m not sure that’s true. Q.isPromise has existed forever. |
00:53 | <ljharb> | a conditional await, i agree. A conditional validation exception tho is fine |
00:53 | <devsnek> | prescribing poor usage of IsPromise |
00:53 | <bakkot> | Kris Kowal: if you're using Q you are aware that there are multiple kinds of promise in the world and want to check specifically for Q 's promises |
00:53 | <bakkot> | if you are a normal JS user you probably are not and should not have to be aware that other people are using other Promise implementations |
00:54 | <bakkot> | so it would not occur to you that Promise.isPromise is not the check you actually want |
00:54 | <ljharb> | imo they shouldn’t be using those other impls :-) |
00:54 | <bakkot> | your opinion is noted but I don't see what relevance it has |
00:55 | <snek> | what if Promise.isPromise(v) returns typeof v.then === 'function' |
00:55 | <bakkot> | there is, empirically, plenty of other code which is using other impls |
00:55 | <ljharb> | agreed |
00:58 | <Kris Kowal> | what if Promise.isPromise(v) returns typeof v.then === 'function' doesn’t need a name. |
00:58 | <Kris Kowal> | The motivation for a brand check is specifically to avoid reentrance from the API of the given value. |
01:00 | <snek> | i mean if you wanna just skip awaiting it |
01:00 | <snek> | that seems like anti-promise behavior |
01:00 | <snek> | the point of promises is to avoid zalgo logic right |
01:00 | <bakkot> | await invokes getters synchronously, is the concern discussed above |
01:01 | <snek> | yeah i saw that in the gh thread |
01:03 | <snek> | i don't quite understand what the concern was with that |
01:03 | <snek> | like "security" was the term used but that seems a bit excessive |
01:04 | <bakkot> | some people are writing code with very strong guarantees |
01:52 | <Mathieu Hofman> | Sorry I had to step away for a minute. Yes the concern is reentrancy, and knowing where that can happen or not. It's not obvious that doing Promise.resolve(x) can trigger synchronous execution of non platform code. And there is no way to prevent it without taking a tick hit for any non-primitive (which is what we're doing now) |
16:37 | <Mathieu Hofman> | So crazy suggestion, but what if we changed Promise.prototype [ @@toStringTag ] to do a IsPromise( this ) before returning "Promise" ? |
16:38 | <Mathieu Hofman> | Um we'd have to make it a get |
16:38 | <Mathieu Hofman> | yeah that would probably break something |
16:57 | <Kris Kowal> | Is Promise.isNativePromise more or less palatable? |
17:02 | <Jack Works> | Is |
17:26 | <nicolo-ribaudo> | Sorry I had to step away for a minute. Yes the concern is reentrancy, and knowing where that can happen or not. It's not obvious that doing Unless I misunderstood what you mean, it can already trigger synchronous code execution:
logs |
18:06 | <Mathieu Hofman> | Yes, the goal is to be able to detect whether something is a native promise without triggering synchronous code execution. So that a "cast" would basically do const cast = (val) => (typeof val !== 'object' && typeof val !== 'function') || !val || isSafePromise(val) ? Promise.resolve(val) : Promise.resolve().then(() => val) |
19:05 | <Kris Kowal> | We discussed promise brand check in the SES meeting today and the good news is that we agree with everyone. (should be processed shortly) https://youtu.be/kLuJZiPUI44 |
19:06 | <Kris Kowal> | In short, we want a brand check and we also would prefer it to be sufficiently obscure that people wouldn’t be tempted to use it to make a Zalgo induced fast lane for non-thenable native promises. |
19:07 | <Kris Kowal> | We already use a sufficiently obscure brand check for TypedArrays in our SES / hardened JavaScript / Lockdown shim, through the TypedArray prototype Symbol.toStringTag getter. |
19:08 | <Kris Kowal> | So we’re currently pondering whether we could navigate into a world where the same trick could be used for Promise. |
19:11 | <Kris Kowal> | I know @ljharb uses the same heinous crime in shims, though no such crime is yet possible with Promise. I assume having any way to brand check would be satisfying, even if that heinous. |
19:36 | <Kris Kowal> | Mathieu Hofman points out that the toStringTag getter trick would break the SES shim and probably other similar programs, so changing the property from value to getter wouldn’t be web compatible. I’m hoping we can come up with a similar trick. Something sufficiently obscure that people wouldn’t be tempted to misconstrue its purpose. |
19:38 | <ljharb> | So crazy suggestion, but what if we changed |
19:41 | <Mathieu Hofman> | Besides the SES shim, I'm wondering what else would break if we did this |
19:43 | <Mathieu Hofman> | The SES shim refuses to initialize if it finds a getter instead of a string value, at least it "fails safe", but unless people update, it's definitely a breaking change. |
20:09 | <ljharb> | the SES shim could be changed tho to just replace the getter, right? |
20:09 | <ljharb> | that seems acceptable for this level of win. But that presumes it’d be an acceptable change to the wider committee |
20:10 | <Mathieu Hofman> | The SES shim can be updated of course, but there are people using it out there that would need to update to unbreak |
20:10 | <bakkot> | what's the proposal for the behavior when IsPromise(this) returns false? throwing seems bad |
20:11 | <bakkot> | but no other option seems obvious to me |
20:11 | <Mathieu Hofman> | return undefined is what TypedArray does |
20:11 | <bakkot> | hm, ok |
20:11 | <bakkot> | did not know TA did that |
20:12 | <bakkot> | oh, because of the shared prototype, right |
20:13 | <Mathieu Hofman> | yeah it causes Object.prototype.toString() to fallback to the builtinTag aka "Object" |
20:14 | <ljharb> | definitely any such getter wouldn't need to throw if it returned a different string when the brand was absent |
20:47 | <bakkot> | Mathieu Hofman: my main takeaway from watching the SES meeting linked above is that not enough people know about the Pin functionality in the spec document |
20:47 | <bakkot> | can recommend: pin things you keep coming back to |
20:57 | <Mathieu Hofman> | Genius, how did I not know about this! |
21:05 | <bakkot> | anyway: I am ok with making Promise.prototye[Symbol.toStringTag] be a getter which does if (IsPromise(this)) return "Promise"; return undefined; . that seems sufficiently obscure that people will only use it in obscure cases, as long as ljharb agrees not to publish an is-promise package on npm |
21:06 | <bakkot> | (fortunately the actual is-promise on npm is just the thenable check) |
21:11 | <ljharb> | lol i mean, i already have IsPromise in es-abstract and it'd definitely use this |
21:11 | <ljharb> | but there wouldn't be much of a need for a separate package |
21:31 | <Ashley Claymore> | Yes, the goal is to be able to detect whether something is a native promise without triggering synchronous code execution. So that a "cast" would basically do |
21:33 | <Mathieu Hofman> | isSafePromise would have to check for the proto |
23:59 | <Justin Ridgewell> | I don't understand how a "is safe promise" won't immediately be misused. |