Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Investigate usage of modified intrinsics with await #65

Closed
ljharb opened this issue Sep 22, 2015 · 16 comments
Closed

Investigate usage of modified intrinsics with await #65

ljharb opened this issue Sep 22, 2015 · 16 comments

Comments

@ljharb
Copy link
Member

ljharb commented Sep 22, 2015

Per discussion:

  • Promise = class MyPromise extends Promise {} ; async function foo() {} - is the Promise returned by foo() a MyPromise? Or is it the original, now mostly-otherwise-inaccessible Promise?
  • await foo; Under what circumstances does this call .then on the object, thus Promise#then (which may be modified)? If I modify Promise.prototype.then, should that be called when await foo, or not? What if I delete the constructor property on foo?

The use cases I'm thinking of are:

  1. I should be able to modify the global Promise in such a way as its impossible for users to ever retrieve the original (I do this now with RegExp and Number, for example, in the es6-shim), and await and async functions should work with the modified global.
  2. I think I should be able to modify Promise.prototype.then such that await foo will always invoke my replacement function.
@benjamingr
Copy link

Or is it the original, now mostly-otherwise-inaccessible Promise?

This makes the most sense to me, consider people doing Promise = ... with a non valid Promise implementation in a library, it shouldn't break other unrelated code.

If I modify Promise.prototype.then, should that be called when await foo?

Most likely yes, though that's an esoteric case that I don't expect to be an issue anyway.

I should be able to modify the global Promise in such a way as its impossible for users to ever retrieve the original.

Why? I don't understand why this is desirable. You can modify the original Promise by changing its then but overriding it completely sounds like it would be problematic. This is kind of like modifying Function.prototype.apply, risky.

I think I should be able to modify Promise.prototype.then such that await foo will always invoke my replacement function.

This sounds OK, but again, why? I'm not sure I understand the use cases here.

@zloirock
Copy link

zloirock commented Oct 2, 2015

Very interesting questions. Currently, most Promise implemented in browsers non-completely spec-complaint. ES7 changes Promise behavior - adds unhandled rejection tracking to the spec. Something else can be changed in the future. For getting correct Promise behavior, it should be polyfilled - patched, wrapped or completely replaced.

@benjamingr
Copy link

That's a very good point, for the very least it should delegate to then if it was overridden

@ljharb
Copy link
Member Author

ljharb commented Oct 3, 2015

The sole use cases I'm asking about is for libraries like es6-shim, es7-shim, core-js, etc, which attempt to fix environmental gaps or breakage (every single environment has ES5 compliance errors, still, for example).

@domenic
Copy link
Member

domenic commented Oct 3, 2015

I don't think the spec should cater to people who are overriding globals but not overriding their accompanying syntax. In other words, if you want to use a version of Promise that is not implemented by your JS engine, then you should use a version of async/await that is not implemented by your engine. Even more explicitly: if you're using the shim, also use a transpiler. (And you probably want to override every web API that returns a promise while you're at it, because those also don't use overridden globals.)

@ljharb
Copy link
Member Author

ljharb commented Oct 3, 2015

The es-shims are only concerned with the language, not with browsers - certainly shims for those would need to use the global Promise.

The es6-shim already handles RegExp and Number by modifying the [[Prototype]] of the original constructor, and presumably it would work the same with async and Promise.

This issue is to simply confirm how it should be spec'ed in this proposal.

For what it's worth, the spec always caters to people who are overriding globals, whether they override their accompanying syntax or not - that's why most things are configurable, even if they're not writable or enumerable, like Symbol.toStringTag on builtins or RegExp on the global object. The same ethos will inevitably apply here - this is a use case the spec and the committee continually insist on preserving, and as such, this particular spec will need to do the same.

@domenic
Copy link
Member

domenic commented Oct 3, 2015

async functions must not use any overridden global.Promise to create their return value, and await syntax must not use any overridden Promise.resolve to resolve its argument. The spec does not cater to overridden builtins, and I'm very surprised you've gotten that mistaken impression. Thinking the configurability of properties has anything to do with how syntax generates objects betrays a fundamental misunderstanding of ES.

RegExp literals don't return overridden RegExp constructor instances. Number or Boolean literals, when wrapped, don't use overridden global.Number or global.Boolean. The ${string} string conversion does not use overridden global.String to perform string conversion. I could go on, but hopefully I don't have to. This is pretty basic stuff. Syntax is the way to avoid modifications to the built-ins.

@ljharb
Copy link
Member Author

ljharb commented Oct 3, 2015

You're totally right - but if I modify the [[Prototype]] chain of the builtin RegExp, and then RegExp = OverriddenRegExp, /a/g instanceof OverriddenRegExp returns true. That's how the es6-shim allows new RegExp(/a/g, 'i') to work. If the same will be possible with Promise and async/await, then I'm content.

@domenic
Copy link
Member

domenic commented Oct 3, 2015

Oh, for sure, I don't see how it would even be possible to prevent that.

@ljharb
Copy link
Member Author

ljharb commented Oct 3, 2015

The one remaining question though is that even if it's the native Promise.resolve, would it still call an overridden Promise.prototype.then? If so, that gives me the mechanism to intervene in the syntax. If not, then I can't override the constructor, just the prototype methods.

I'm specifically looking at http://www.ecma-international.org/ecma-262/6.0/#sec-promise-executor step 8, which leads to http://www.ecma-international.org/ecma-262/6.0/#sec-createresolvingfunctions step 2, which leads to http://www.ecma-international.org/ecma-262/6.0/#sec-promise-resolve-functions step 8, which does a [[Get]] of "then" on the promise value. If I've modified Promise.prototype.then on the builtin Promise, then surely this is what will be called by await, per http://www.ecma-international.org/ecma-262/6.0/index.html#sec-newpromisecapability step 6 which leads to http://www.ecma-international.org/ecma-262/6.0/index.html#sec-promise-executor step 8?

@bterlson
Copy link
Member

bterlson commented Oct 3, 2015

Changes to %Promise% and %PromisePrototype% should not affect async function and await semantics, although the standard built-in Promise Resolve Function does do a [[Get]] for the awaited promise's then since it could be any foreign thenable. If you await a native promise, Promise.prototype.then will be invoked.

@benjamingr
Copy link

If you await a native promise, Promise.prototype.then will be invoked.

Yes I think this is the most consistent behavior. You delegate to Promise.prototype.then but if someone swaps out the global Promise object async functions still return an instance of the original.

@domenic
Copy link
Member

domenic commented Oct 3, 2015

It won't use .then unless you also override .constructor, since otherwise IsPromise will be true and it will do a pass-through.

@bterlson
Copy link
Member

bterlson commented Oct 3, 2015

This is a difference between Promise.resolve and the Promise Resolve Function. Async function await uses the latter and does the equivalent of var p = new Promise(extractor); extractor.resolve(awaitedValue); return p;, and this codepath should always invoke .then on the awaited value. I think this is the appropriate semantics.

@ljharb
Copy link
Member Author

ljharb commented Oct 3, 2015

Great! So that means that returning from an async function returns an original Promise even if the global is modified, and await invokes Promise.prototype.then even if it has been modified.

Similarly, if I do something along the lines of:

var OriginalPromise = Promise;
var MiddleMan = function () {};
Object.setPrototypeOf(OriginalPromise, MiddleMan);
MiddleMan.prototype = OriginalPromise.prototype;
OriginalPromise.prototype.constructor = MiddleMan;
Promise = MiddleMan;

then var promise = (async function () {}()); promise instanceof OriginalPromise && promise instanceof MiddleMan should be true, even though the original Promise is no longer user-accessible except via OriginalPromise. This is how the es6-shim currently overrides Number and RegExp, even though both are produced by syntactic literals. (I wrote this code off the top of my head so corrections to it aren't necessary or desired).

I'm going to close this since this has answered my original question perfectly.

@ljharb ljharb closed this as completed Oct 3, 2015
@bterlson
Copy link
Member

bterlson commented Oct 3, 2015

Yeah, I believe it should work fine for your use case. You can try in Edge if you have a windows machine ;)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants