Darien Valentine (2018-07-20T11:29:34.000Z)
In `Promise.prototype.then`:

> 1. Let promise be the this value.
> 2. If IsPromise(promise) is false, throw a TypeError exception.
> [ ... ]

In `Promise.prototype.finally`:

> 1. Let promise be the this value.
> 2. If Type(promise) is not Object, throw a TypeError exception.
> [...]

In `Promise.prototype.catch`:

> 1. Let promise be the this value.
> 2. Return ? Invoke(promise, "then", « undefined, onRejected »).

First, this means that only `then` requires the this value to be a Promise:

```js
for (const key of [ 'then', 'finally', 'catch' ]) {
  try {
    Promise.prototype[key].call({
      then: () => console.log(`${ key } doesn’t brand check its this value`)
    });
  } catch (err) {
    console.log(`${ key } does brand check its this value`);
  }
}

// > then does brand check its this value
// > finally doesn’t brand check its this value
// > catch doesn’t brand check its this value
```

Second, note that `Invoke` uses `GetV`, not `Get`. Thus:

```js
for (const key of [ 'then', 'finally', 'catch' ]) {
  try {
    String.prototype.then = () =>
      console.log(`${ key } casts this value to object`);

    Promise.prototype[key].call('foo');
  } catch (err) {
    console.log(`${ key } doesn’t cast this value to object`);
  }
}

// > then doesn’t cast this value to object
// > finally doesn’t cast this value to object
// > catch casts this value to object
```

On reflection, I think I see the logic to this:

- `Promise.prototype.then` ends up executing `PerformPromiseThen`, which
requires its first argument to be a native promise object.
- `Promise.prototype.finally` ends up executing `SpeciesConstructor`, which
requires its first argument to be an object.
- `Promise.prototype.catch` does neither.

However the inconsistency within this trio seems pretty odd to me. I
suppose I would have expected them all to be as constrained as the most
constrained method needed to be for the sake of uniformity, given that they
constitute a single API. Conversely, if the goal was for each method to be
exactly as lenient as is possible, then `finally` seems to be
over-constrained; it seems like `C` could have just defaulted to `Promise`
in cases where `SpeciesConstructor` wasn’t applicable, making it as lenient
as `catch`.

I wasn’t able to find prior discussion about this, though it’s a bit hard
to search for, so I may be missing it. Do these behaviors seem odd to
anyone else, or is it what you’d expect?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180720/85ed179d/attachment-0001.html>
valentinium at gmail.com (2018-07-20T11:31:24.106Z)
In `Promise.prototype.then`:

> 1. Let promise be the this value.
> 2. If IsPromise(promise) is false, throw a TypeError exception.
> [ ... ]

In `Promise.prototype.finally`:

> 1. Let promise be the this value.
> 2. If Type(promise) is not Object, throw a TypeError exception.
> [...]

In `Promise.prototype.catch`:

> 1. Let promise be the this value.
> 2. Return ? Invoke(promise, "then", « undefined, onRejected »).

First, this means that only `then` requires the this value to be a Promise:

```js
for (const key of [ 'then', 'finally', 'catch' ]) {
  try {
    Promise.prototype[key].call({
      then: () => console.log(`${ key } doesn’t brand check its this value`)
    });
  } catch (err) {
    console.log(`${ key } does brand check its this value`);
  }
}

// > then does brand check its this value
// > finally doesn’t brand check its this value
// > catch doesn’t brand check its this value
```

Second, note that `Invoke` uses `GetV`, not `Get`. Thus:

```js
for (const key of [ 'then', 'finally', 'catch' ]) {
  try {
    String.prototype.then = () =>
      console.log(`${ key } casts this value to object`);

    Promise.prototype[key].call('foo');
  } catch (err) {
    console.log(`${ key } doesn’t cast this value to object`);
  }
}

// > then doesn’t cast this value to object
// > finally doesn’t cast this value to object
// > catch casts this value to object
```

On reflection, I think I see the logic to this:

- `Promise.prototype.then` ends up executing `PerformPromiseThen`, which
requires its first argument to be a native promise object.
- `Promise.prototype.finally` ends up executing `SpeciesConstructor`, which
requires its first argument to be an object.
- `Promise.prototype.catch` does neither.

However the inconsistency within this trio seems pretty odd to me. I suppose I would have expected them all to be as constrained as the most constrained method needed to be for the sake of uniformity, given that they
constitute a single API. Conversely, if the goal was for each method to be exactly as lenient as is possible, then `finally` seems to be over-constrained; it seems like `C` could have just defaulted to `Promise` in cases where `SpeciesConstructor` wasn’t applicable, making it as lenient as `catch`.

I wasn’t able to find prior discussion about this, though it’s a bit hard to search for, so I may be missing it. Do these behaviors seem odd to anyone else, or is it what you’d expect?