7

Having the following pice of code...

const array = [
  Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)
];

Array.prototype.then = function () {
  console.log('Why does this gets triggered?');
}

Promise.all(array)
  .then(result => console.log(result))

Why does Promise.all() by itself calls my .then() proto function on the Array?

Of course it must call .then() for each of the elements in the array. That’s obvious. But what is the purpose of doing it over the Array itself?

This behavior is happening on V8

To consider: If you change Promise.all() to Promise.race() this does not happen.

I'm not saying this is a mistake. I just want to understand the reason. If you can quote the EcmaScript specification on the answer I would really appreciate.

Update: I know Promise.all() returns an array but wrapped on a promise. That is obvious too. If you remove the .then() like...

Promise.all(array)

it still executes the .then()method.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
Iván E. Sánchez
  • 1,183
  • 15
  • 28
  • 5
    This being a prime example of why it’s a bad idea to extend native types... – Heretic Monkey Jan 06 '20 at 16:38
  • 3
    If this was real code: absolutely. But as a question about unexpected behaviour on the Array prototype during Promise.all, it's a _very_ interesting question, the answer to which probably involves the deeper parts of the Promise specification. – Mike 'Pomax' Kamermans Jan 06 '20 at 16:45
  • `Promise.all()` presumes its parameters are "thenables"; by adding a `then` property to the array, it became a "thenable". – Haroldo_OK Jan 06 '20 at 16:46
  • 3
    @Haroldo_OK got specs or docs for that? Because [the MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) only mention that the input has to be an iterable, which in no way explains why _its_ `then` gets called, rather than `then` on each of the elements inside that iterable. – Mike 'Pomax' Kamermans Jan 06 '20 at 16:48
  • 1
    Maybe it's that inside `Promise.all()`, when all the elements are resolved, the code itself effectively performs a `resolve()` with the array of values from the promises. *That* array will *also* have a `.then()` from the prototype, so the `resolve()` code thinks it's seeing a Promise instance too. – Pointy Jan 06 '20 at 16:54
  • @Mike'Pomax'Kamermans Exactly, and if you take a look at some of the polyfills out there for `Promise.all()` none of them makes a call to the array.then() method https://gist.github.com/Rich-Harris/11010768#file-promise-js-L92 – Iván E. Sánchez Jan 06 '20 at 16:54
  • Indeed if you add `console.log(this)` in the `.then()` callback, the array is not the *original* array, it's the resolution values from the original array; in other words, the resolution value of the call to `Promise.all()`. – Pointy Jan 06 '20 at 16:56

1 Answers1

3

When a resolve() is called, and the value passed to it has a .then property that refers to a function, the normal Promise mechanism will invoke that function. In this case, internal to Promise.all() an array of resolution values is built as each Promise in the source array is resolved. When that finishes, the innards of Promise.all() will call its own resolve(), passing in the array of resolved values.

Well that array will also have a .then() value, inherited from the Array prototype. Thus that resolve() call will invoke the .then() method just like any other Promise resolution would.

Promise resolve() in the spec

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • +1 Right, for more details see step 10. b. from [Promise.all Resolve Element Functions](https://www.ecma-international.org/ecma-262/7.0/#sec-promise.all-resolve-element-functions). – Christian C. Salvadó Jan 06 '20 at 17:28
  • Let me see If I get what you're saying. This has nothing to do with the `Promise.all()` method. If I wrap any promise inside another promise the interpreter will immediately resolve the internal promise. So I could never have a promise of a promise? But it does that, right when I pass the "thenable" to the resolve method. A little bit confuse to me. – Iván E. Sánchez Jan 06 '20 at 17:45
  • 1
    @IvánSánchez it *is* definitely confusing. When you `resolve()` a promise and pass a Promise (or any "thenable"; any object with a `.then` method) as the value, the Promise mechanism invokes the `.then()` method with a resolve and reject parameters as if it were a callback function you'd pass to `new Promise()`. Exactly why that makes sense kind-of escapes me; I think I understood it at one point but I'd need to re-read some good explanation. However that's clearly what's happening in your posted code. – Pointy Jan 06 '20 at 17:48
  • Well, I still believe it's a little weird that the resolution of the inner promise happens when it's passed as the argument for the `resolve()` method instead of at the end with the `.then()` but, it is what it is. Sadly for me, this means that my proposal idea could never been make: https://es.discourse.group/t/allow-awaiting-on-arrays/178 Thanks for your time man – Iván E. Sánchez Jan 06 '20 at 18:11