2

I have an array of userIds that I use in getOrdersByUserId() to get the orders placed by these users for a specific month:

function getOrdersByUserId(userId, month = 4) {
    const apiService = new ApiService();

    return apiService.getOrdersList(`?OrderingUser=${userId}`)
        .then(orders => {
            const monthOrders = orders.filter(order => new Date(order.FromTime)
            .getMonth() === month);

            return monthOrders;
        });
}

Here's getOrdersList() in ApiService:

getOrdersList(queryString = '') {
    return httpsRequest.createRequest(this.URL.ordersList + queryString, {}, this.requestHeaders, 'GET')
        .then(result => JSON.parse(result).Records);
}

httpsRequest.createRequest returns a promise that resolves with the response from the API (I can share that code too if necessary).

When I test getOrdersByUserId() with 8 userIds I have, I get the correct records every time. Where this breaks is when I put these calls into a promise chain and execute them with Promise.All(). I wrote the below code with help from this answer: Wait for forEach with a promise inside to finish

const promises = userIds.map(userId => {
            return getOrdersByUserId(userId, month)
                .then(orders => {
                    return orders;
                });
        });

        Promise.all(promises).then(results => {
            console.log(results);
        }).catch(err => {
           console.log(err);
        });

Testing with the 8 userIds I get this error four or five times:

(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): SyntaxError: Unexpected end of JSON input

After a lot of console logging it seems that this error occurs when httpsRequest.createRequest() gives a result that is an empty string, instead of a JSON response from the API. So why would all of these calls with the same userIds work individually, but break when executed in a promise chain? How can I resolve this?

no_parachute44
  • 365
  • 1
  • 15

1 Answers1

2

You have a common misunderstanding: You don't execute promises. Promise.all doesn't "run" anything. A promise is just a means of watching an operation to know when it's done and whether it worked or failed.

In your case, the operations are started by apiService.getOrdersList, as soon as you call it.

What you're seeing suggests that

  1. The API service doesn't like you sending it eight simultaneous requests (perhaps it rate-limits), and

  2. The promise from the API service resolves with a value that isn't valid JSON rather than rejecting when it can't handle #1 (which is unfortunate, it should reject, not resolve).

Nothing about using Promise.all breaks these operations. But apparently, running eight of these operations in parallel breaks.

You could run them in series (one after another):

userIds.reduce((p, userId, index) => {
    return p.then(results => {
        return getOrdersByUserId(userId, month)
         .then(orders => {
             results[index] = orders;
             return results;
         });
    });
}, Promise.resolve([]))
.then(results => {
    // `results` is an array of results, in the same order as `userIds`
})
.catch(err => {
   console.log(err);
});

Each call to getOrdersByUserId waits for the previous one to complete; the final result is an array of results in the same order as the userIds array.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks so much for the thoughtful reply! However I get an error running this, "ReferenceError: p is not defined". Do you know what's causing this? – no_parachute44 Jun 20 '18 at 14:54
  • @no_parachute44 - Sorry, that was a typo. It should be `acc.p = acc.p.then...`. – T.J. Crowder Jun 20 '18 at 14:58
  • Ok now I'm getting "userIds.reduce(...).then is not a function" as well as five of the errors first noted in my question. – no_parachute44 Jun 20 '18 at 15:00
  • @no_parachute44 - Sorry, mis-remembered the pattern. Fixed above (one hopes). – T.J. Crowder Jun 20 '18 at 15:06
  • 1
    TJ, you are a God among men! Thank you so, so much, I can't tell you how much this helps me. Hope you have a great day :) – no_parachute44 Jun 20 '18 at 15:10
  • @no_parachute44 - LOL! A God amongst men wouldn't have got it wrong twice first. :-) I'm really glad that helped! – T.J. Crowder Jun 20 '18 at 15:13
  • One last question, how do I get that last results variable out of the function? getOrdersByUserId().then(result => console.log(result)); logs "undefined" @T.J. Crowder – no_parachute44 Jun 20 '18 at 15:37
  • @no_parachute44 - Your `getOrdersByUserId` function returns a promise of the value it gets from `Records` on the parsed JSON. If you're getting `undefined`, that property's value is `undefined` or the property doesn't exist. – T.J. Crowder Jun 20 '18 at 15:48
  • Below where your comment "// `results` is an array of results," is, I return results. If I console log it there it shows the correct information. Is that not the place to do it? How should I be returning results then? – no_parachute44 Jun 20 '18 at 15:51
  • @no_parachute44 - If all of this is in a function, then: 1. Add `return` just before `userIds.reduce`, 2. Remove the `catch` handler, that's the caller's job, and 3. Remove the `then` handler that has only that comment in it, the caller will get results when it consumes the promise. – T.J. Crowder Jun 20 '18 at 15:56