0

I have this piece of code:

const Axios = require('axios');
const baseURL = 'https://jsonplaceholder.typicode.com';

async function main() {
    const posts = await Axios(`${baseURL}/posts`);
    const users = await Promise.all(posts.data.map( async (post, index) => {
        let d = await Axios(`${baseURL}/users/${post.userId}`);
        return d.data.name;
    }))

    users.map( (user, index) => {
        console.log( `${index}. ${user}` );
    });
}

And outputs the result in order:

1. Patricia
2. Glenna
3. Nicholas
4. Kurtis
5. Chelsey

Everything Ok, but ... If I do:

async function main() {
    const posts = await Axios(`${baseURL}/posts`);
    await Promise.all(posts.data.map( async (post, index) => {
        let d = await Axios(`${baseURL}/users/${post.userId}`);
        console.log( `${index}. ${d.data.name}` );
    }))
}

the console.log inside map prints the list unordered ...

2. Glenna
4. Kurtis
5. Chelsey
1. Patricia
3. Nicholas

My question is:

Why in the first code map returns the list ordered, and in the second one the console.log inside map, prints the list unordered ?

robe007
  • 3,523
  • 4
  • 33
  • 59
  • `.map()` does not pay attention to the promise that your async callback is returning so it doesn't wait for it to resolve. Thus, all you're doing is launching a bunch of async operations at once and then seeing which ones finish first. – jfriend00 Dec 01 '18 at 01:55
  • @jfriend00 So, the `await` does not stops the `.map()` iteration process while is waiting for the promise (Axios) result? – robe007 Dec 03 '18 at 17:39
  • 1
    Yes, that's correct. Your `.map()` does not wait for the `await` inside the callback. So, the `console.log()` statements inside your second `.map()` are just a race between all the requests you've launched and they can finish in any order. `Promise.all()` collects them all and puts them in proper order for you so when you look at the results of `await Promise.all()` you get them back in order. – jfriend00 Dec 04 '18 at 05:31

4 Answers4

4

Promise.all is designed to keep the order of the results of the promises that were passed to it, independent of the order those promises actually resolved. So when Promise.all resolves, it means all individual promises resolved, and then Promise.all resolves to the array of resolutions in the same order as the corresponding promises have in the array.

If however you output the values as soon as their promises resolve, then the above has of course no effect on your output -- it will now be ordered by the order in which the individual promises resolve.

Simple example:

Let's say there are three promises p1,p2, p3 that resolve to 1, 2 and 3. But the second promise will resolve sooner than the other two.

Promise.all is called with [p1, p2, p3]. It returns a new promise that will eventually resolve to [1, 2, 3]. During the time that not all promises are resolved, you could imagine that an internal array evolves as follows:

  • p2 resolves. Promise.all internally stores [undefined, 2, undefined]
  • p1 resolves. Promise.all internally stores [1, 2, undefined]
  • p3 resolves. Promise.all also resolves with value [1, 2, 3]

Here you can see that the first code would output 2, 1, 3 while the second would output 1, 2, 3

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Who keeps the order in my first code: `Promise.all` or `.map()`? – robe007 Dec 03 '18 at 22:05
  • Another Q: The `await Axios(${baseURL}/users/${post.userId})` stops the `.map()` iteration process while is waiting for the promise (Axios) result? – robe007 Dec 03 '18 at 23:33
  • 1
    `map` also keeps order but is synchronous. In your first code it returns promises keeping order. `Promise.all` returns just one promise, but it will resolve asynchronously to an array that reflects the original order. – trincot Dec 04 '18 at 07:43
  • 1
    The inner `await Axios` does not stop the `map` process. `await` makes the callback to `map` return immediately with a new promise. All those created promises are returned by `map` immediately. – trincot Dec 04 '18 at 07:50
  • Amazing. I have always thought that `await` stopped the execution *where it is*, until returns the result of the promise. – robe007 Dec 04 '18 at 20:34
  • 1
    Indeed, `await` is not blocking. The first one in an `async` function actually makes that function return a promise, and code execution continues after that function call. Only when the JS call stack is empty, and the `await`-ed promise resolves, will the function's context be *restored*, and then execution continues right after that `await`. See also explanations [here](https://stackoverflow.com/a/44410481/5459839) and [here](https://stackoverflow.com/a/51583530/5459839). – trincot Dec 04 '18 at 20:42
1

Because if you use asynchronus code it doesnt matter in which order you "fired" the requests, it only counts how long the response take.

So your result will be ordered in the way your requests finished, so if your request for x finished first, even if you fired it last, it will be still on the first position of your result

The map function is "blocking" which means that the second request is fired after the first is finished and so on.

here an example: https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/

Niczem Olaske
  • 271
  • 1
  • 4
  • 13
1

When you use Promise.all(requests), all requests are made in parallel, therefore you can't know which ends before another.

In the first code you already had the result in the order of the array of requests. But in the second one, the console.log is in the order of responses.

1

Because Promise.all executes promises in parallel and is async, while .map is blocking and executes with order and it won't finish until it iterates over all the items. It's like for-each loop.

If you want to achieve with ordering, either I suggest you to use Bluebird.each (library) or something like this:

const promiseEach = promises => {
  const results = [];

  return promises
    .reduce((acc, val, idx) => acc.then(_ => ((idx > 0 && results.push(_)), val)), Promise.resolve())
    .then(_ => [...results, _]);
}

const a1 = Promise.resolve(1);
const a2 = Promise.resolve(2);
const a3 = Promise.resolve(3);

const d1 = new Promise((resolve, reject) => { setTimeout(() => resolve(1), 3000); }); // resolves after 3 seconds.
const d2 = new Promise((resolve, reject) => { setTimeout(() => resolve(2), 2000); }); // resolves after 2 seconds.
const d3 = new Promise((resolve, reject) => { setTimeout(() => resolve(3), 1000); }); // resolves after 1 seconds.

// this will respect orderings, before first promise is not resolved, does not goes to next one.
promiseEach([a1, a2, a3, d1, d2, d3]).then(console.log);

and pass your map to promiseEach function and they will be ordered.

Nika
  • 1,864
  • 3
  • 23
  • 44
  • Can you please elaborate your `reduce` in detail? I get lost on it – robe007 Dec 03 '18 at 18:03
  • 1
    Oh.. hmm. What it does is, making promises to chain each other. For example, if you have N promises (a1, a2, ... aN) this reduce will produce something like: `Promise.resolve().then(() => a1).then(() => a2).then(...).then(() => aN)` (but also returns results returned from each chain) which means, unless first promise is not resolved, it won't continue to next chain and so on. – Nika Dec 03 '18 at 20:41
  • EDIT2: I suggest you to take a look at bluebird's each method (http://bluebirdjs.com/docs/api/promise.each.html) – Nika Dec 03 '18 at 20:44
  • Ok. I'll take a look to ***Bluebird's each***. And about the `reduce`, I have made it in this way: `const promiseEach = async (promises) => promises.reduce(async (acc, val) => { const collection = await acc; collection.push(await val); return collection }, (async () => [])())` **Javascript is awesome !** – robe007 Dec 04 '18 at 20:22
  • Ehm.. I would not suggest that ;d first of all, you have extra `async` which you don't really need (`async (promises) => ...`), second you can pass only array (not `async () => []`), so in the end it will become something like this: `const promiseEach = promises => promises.reduce(async (acc, val) => { acc.push(await val); return acc; }, []);` – Nika Dec 05 '18 at 17:12
  • EDIT: whatever you're doing there, you can just write with for-loop, it's the same then. `const result = []; for (let i = 0; i < promises.length; i++) { result.push(await promises[i]); } return result;` but it's not a good practice to write async code in loops though, what I wrote there is different. – Nika Dec 05 '18 at 17:14
  • By saying "not a good practice", I mean they're executed immediately (just like `Promise.all` does) instead of waiting for previous one to finish. – Nika Dec 05 '18 at 17:18