Executing promises in array with Promise.all break

2019-02-26 05:27发布

问题:

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?

回答1:

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.