<div dir="ltr">That seems reasonable, although I'm not sure of the value of that consistency. It'd have to be proven to be web-compatible - ie, if anyone was adding a `.then` to a primitive prototype, they might be relying on the current behavior of `.catch`.</div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Jul 20, 2018 at 9:58 PM, Darien Valentine <span dir="ltr"><<a href="mailto:valentinium@gmail.com" target="_blank">valentinium@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div>That makes sense for sure, but I don’t think subclassing is impacted by receiver constraints in this regard either way, since subclasses will still be “IsPromise” promises. What is affected is portability of the methods to “non-IsPromise” thenables. Catch not requiring an IsPromise receiver makes sense from that angle:</div><div><br></div><div>```js</div><div>OffbrandPromise.prototype.<wbr>catch = Promise.prototype.catch;</div><div>```</div><div><br></div><div>But if portability to non-native, non-Promise-subclass thenables is the goal, I’d still have expected finally to be the same “level” of generic as catch. Neither require their receiver to be an IsPromise-promise, but catch doesn’t even require its receiver to be an object, while finally does. This is a very minor thing obviously, but I wondered if it might still be web safe at this point to make catch require its receiver to be an object like finally does. It would be more consistent, and it’s pretty hard to imagine the current ability to call catch on a primitive, as in the second example above, being a useful behavior.</div></div><div class="HOEnZb"><div class="h5"><br><div class="gmail_quote"><div dir="ltr">On Fri, Jul 20, 2018 at 7:10 PM Jordan Harband <<a href="mailto:ljharb@gmail.com" target="_blank">ljharb@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr">This is intentional - `catch` delegates to `then`, so that a subclass that overwrites `then` doesn't have to also override `catch` (and the same for `finally`, which also calls into `then`).</div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Jul 20, 2018 at 4:29 AM, Darien Valentine <span dir="ltr"><<a href="mailto:valentinium@gmail.com" target="_blank">valentinium@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div>In `Promise.prototype.then`:</div><div><br></div><div>> 1. Let promise be the this value.</div><div>> 2. If IsPromise(promise) is false, throw a TypeError exception.</div><div>> [ ... ]</div><div><br></div><div>In `Promise.prototype.finally`:</div><div><br></div><div>> 1. Let promise be the this value.</div><div>> 2. If Type(promise) is not Object, throw a TypeError exception.</div><div>> [...]</div><div><br></div><div>In `Promise.prototype.catch`:</div><div><br></div><div>> 1. Let promise be the this value.</div><div>> 2. Return ? Invoke(promise, "then", « undefined, onRejected »).</div><div><br></div><div>First, this means that only `then` requires the this value to be a Promise:</div><div><br></div><div>```js</div><div>for (const key of [ 'then', 'finally', 'catch' ]) {</div><div>  try {</div><div>    Promise.prototype[key].call({</div><div>      then: () => console.log(`${ key } doesn’t brand check its this value`)</div><div>    });</div><div>  } catch (err) {</div><div>    console.log(`${ key } does brand check its this value`);</div><div>  }</div><div>}</div><div><br></div><div>// > then does brand check its this value</div><div>// > finally doesn’t brand check its this value</div><div>// > catch doesn’t brand check its this value</div><div>```</div><div><br></div><div>Second, note that `Invoke` uses `GetV`, not `Get`. Thus:</div><div><br></div><div>```js</div><div>for (const key of [ 'then', 'finally', 'catch' ]) {</div><div>  try {</div><div>    String.prototype.then = () =></div><div>      console.log(`${ key } casts this value to object`);</div><div><br></div><div>    Promise.prototype[key].call('<wbr>foo');</div><div>  } catch (err) {</div><div>    console.log(`${ key } doesn’t cast this value to object`);</div><div>  }</div><div>}</div><div><br></div><div>// > then doesn’t cast this value to object</div><div>// > finally doesn’t cast this value to object</div><div>// > catch casts this value to object</div><div>```</div><div><br></div><div>On reflection, I think I see the logic to this:</div><div><br></div><div>- `Promise.prototype.then` ends up executing `PerformPromiseThen`, which requires its first argument to be a native promise object.</div><div>- `Promise.prototype.finally` ends up executing `SpeciesConstructor`, which requires its first argument to be an object.</div><div>- `Promise.prototype.catch` does neither.</div><div><br></div><div>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`.</div><div><br></div><div>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?</div></div>
<br>______________________________<wbr>_________________<br>
es-discuss mailing list<br>
<a href="mailto:es-discuss@mozilla.org" target="_blank">es-discuss@mozilla.org</a><br>
<a href="https://mail.mozilla.org/listinfo/es-discuss" rel="noreferrer" target="_blank">https://mail.mozilla.org/<wbr>listinfo/es-discuss</a><br>
<br></blockquote></div><br></div>
</blockquote></div>
</div></div></blockquote></div><br></div>