How to log values from Promise.all

I have a function that returns a Promise.all() like this:

return Promise.all(promises).catch(err => console.log(err))

Then I try to log the values like this

myFunction()
.then(values => console.log(values));

The result I get is:

[
    Response (22 MB) {
      ok: true,
      url: "XXX",
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (22 MB)
    }, Response (22 MB) {
      ok: true,
      url: "XXX",
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (22 MB)
    }, Response (21 MB) {
      ok: true,
      url: "XXX",
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (21 MB)
    }, Response (22 MB) {
      ok: true,
      url: "XXX",
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (22 MB)
    }, Response (22 MB) {
      ok: true,
      url: "XXX",
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (22 MB)
    }, Response (20 MB) {
      ok: true,
      url: "XXX",
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (20 MB)
    }, Response (23 MB) {
      ok: true,
      url: "XXX"
      statusText: "OK",
      redirected: false,
      bodyUsed: false,
      status: 200,
      Blob (23 MB)
    }
  ]

However, I cannot log the values whichever way I do it. With a regular Promise, I have always been able to do it using the await operator. I have also tried it in this case, but all I get are the Response objects.

MDNs own example that can be seen HERE does not include the await operator and they are still able to log the values:

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

I suspect I am having this challenge because I am working with Promise.all() and it somehow behaves differently compared to a normal promise.

Does anyone understand what I may be doing wrong?

Thank you for being so supportive.

UPDATE (FIX): I tried a different pattern with try... catch that I found HERE, which worked. However, it annoys me that I cannot understand why.

One thing is that they use two Promise.all(). Why is that?
They also use full await, although that should not make any difference

Yeah, Promises are awkward. But they are also important.

It’s not clear exactly what you are trying to do. Can you break it down into a simple example?

As far as dealing with them, I find async/await to make it easier.

If I understand what you are trying to do, I would expect something like this:

const fetchData = async (urls) => {
  try {
    const data = await Promise.all(
      [
        fetch('https://jsonplaceholder.typicode.com/todos/1').then(r => r.json()),
        fetch('https://jsonplaceholder.typicode.com/users/1').then(r => r.json()),
      ]
    )
    console.log('fetchData response:', data)
    return data
  } catch (err) {
    console.log('Error:', err)
    return null
  }
}

const doTheThing = async () => {
  const response = await fetchData()
  console.log('doTheThing response', response)
}

doTheThing()

Dealing with await can be difficult on the top level, but I think top level await went in recently, but I don’t know for which versions of JS or what polyfills there are.

One thing is that they use two Promise.all(). Why is that?

Do you mean this?

const fetchNames = async () => {
      try {
        const res = await Promise.all([
          fetch("./names.json"),
          fetch("./names-mid.json"),
          fetch("./names-old.json")
        ]);
        const data = await Promise.all(res.map(r => r.json()))
        console.log(data.flat());
      } catch {
        throw Error("Promise failed");
      }
};

One does the fetching, which returns promises. Then the second goes through and handles those promises.

Thank you @kevinSmith :pray:

Yeah, Promises are awkward. But they are also important.

I agree. I thought I understood regular promises well. I seem to have found a pattern with regular promises (as opposed to Promise.all and Promise.allSettled) that usually works out the first time. However, when I tried working with Promise.all I ran into difficulties and experienced a lot of trial and error before I could make it work. As a result, I have started to doubt how much I actually understand about promises.

I am sharing my entire code below to avoid theoretical examples and unnecessary confusion (on my part).

With the code, I am trying to fetch data from an API that I wish was paginated by having a next_token at the end of the json response that I could use with a generator function to keep on fetching data until there is no more token. Instead, because of how this particular API is made, I have to first do a different request and some logic to figure out how many pages I need to fetch. I could do a similar thing where I keep on fetching until there is no more data in the json response, but I wanted to make my code asynchronous. This is beside the point, but I want to explain why the code looks the way it does.

The code is not that complicated; basically, it uses a separate function baseRequest (you suggested this pattern a few months ago in a different thread - thanks you :pray:) and generates promises (i.e. constructs different URIs as arguments for the baseRequest in a loop).

 async getBulkFundamentals(opts: FundamentalsParams) {
   opts.limit ||= 500;
   opts.pageLimit ||= Math.ceil(opts.symbols.length / opts.limit);

   let promises: Promise<Response>[] ;
   for (let i = 0; i < opts.pageLimit; i++) {
     const offset = i * opts.limit;
     const url = `${endpoints.rest.BULK_FUNDAMENTALS}${opts.exchange}?api_token=${process.env.API_TOKEN}&version=1.2&fmt=json&offset=${offset}`;

     promises.push(
       this.baseRequest({
         url,
         method: Method.Get,
         errorMessage: "Error retrieving bulk fundamentals",
       })
     );
   }

   try {
     const responses = await Promise.all(promises);
     return responses;
   } catch (error) {
     throw new Error("Promise.all failed");
   }
 }

Then I call the function like this:

myClient
  .getTickers("NYSE")
  .then((resp) => resp.json() as unknown as Tickers[])
  .then((json) =>
    json
      .filter((ticker) => ticker.Type === "Common Stock")
      .map((ticker) => ticker.Code)
  )
  .then((symbols) => {
    myClient
      .getBulkFundamentals({
        exchange: "NYSE",
        symbols: symbols,
        pageLimit: 2,
      })
      .then((responses) =>
        responses.map(async (response) => {
          const page = await response.json();
          console.log(page);
        })
      );
  });

As you can see here, I only use one Promise.all and it seems to work. But it also works with two Promise.all, i.e. if I modify the try block above like this:

    try {
      const responses = await Promise.all(promises);
      const data = await Promise.all(responses);
      return data
    }

What is the purpose of two Promise.all and why does it seem to work both ways in my example. There must be a difference, but I can’t figure out what it is. Both your example and the MDN example use two Promise.all but it doesn’t actually seem to be necessary (at least not in my example). When is it strictly necessary to use two Promise.all instead of only one?

I understand my question is quite vague. That is because I do not know exactly where my lack of understanding lies. If you see anything in my code that does not make sense or can be improved, pointing it out to me is extremely helpful. In the other threads I have posted, your comments, even though they are general, have introduced new concepts and terminology that I have not been aware of, which in turn have helped me connect the dots.

A bonus question is whether I am using the type casting correctly above. I read (literally a few minutes ago) that type casting is a dangerous pattern in TypeScript that should be avoided if possible. If I do not use type casting like I have done, TypeScript gives me problems when it does not know what types it is dealing with.

I can’t see what your baseRequest is doing - I assume it is returning fetches? Or the promises returned by fetch?

Break this out into a simple app that only does the bare minimum - it just sends a bunch of urls or whatever.

What is the purpose of two Promise.all and why does it seem to work both ways in my example.

From the docs:

The Promise.all() method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input’s promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the input’s promises rejects, with this first rejection reason.

So, you use it if you want the root Promise to only resolve once all the other Promises resolve. I would use this if I only wanted the values once they have all returned. They should all be independent - one does not depend on the others. Sometimes you want that, sometimes you don’t.

Thank you @kevinSmith , your answer makes sense. I’ll keep on practising on using Promise.all to hopefully gain a good understand of it.

1 Like