3

I am using ES6 JavaScript and making API calls that are reliant on the order in which they are returned. The http client is Axios. A colleague has instructed me to use Promise.all(). It works and I know it's guaranteed to, but I am unsure how it can be guaranteed that the results are in order. My understanding is that asynchronous requests are not guaranteed! My simplified code is:

Promise.all([
    axios.get('/cars'),
    axios.get('/road-conditions')
]).then(values => {

    this.cars = values[0]
    this.roadConditions = values[1]

})

I would like to understand how values knows which request is which. Is this a special feature with Axios?

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • `Promise.all` gives you resolved values in the same order as the array of promises you passed to it. What’s not guaranteed here is the order in which the requests reach the server. – Ry- Feb 11 '18 at 21:13
  • 1
    Thank you for the response, I do understand the order in the array is respected, I am just not clear how the promise guarantees the order. –  Feb 11 '18 at 21:14
  • 1
    Promise.all waits until all promises in the array has been fulfilled, before it will be fulfilled. Therefore you get the answers in the order specified, regardless of the order it was received. – some Feb 11 '18 at 21:15

1 Answers1

5

There's no great trick to it: Promise.all just remembers the index of the promise and saves that promise's result to the correct slot in the array it builds. It doesn't just use push to build the array (as that would indeed be chaotic).

Here's an example of doing it showing you very roughly what's going on under the covers:

function randomDelay(val) {
    return new Promise(resolve => {
        setTimeout(
            resolve,
            Math.random() * 1000,
            val
        );
    });
}

// See: https://tc39.es/ecma262/#sec-performpromiseall
function fakeAll(iterable) {
    return new Promise((resolve, reject) => {
        const values = [];
        let remainingElementsCount = 1;
        let index = 0;
        for (const value of iterable) {
            const thisIndex = index;     // Remember the index for this result
            Promise.resolve(value)       // To handle thenables and raw values
            .then(val => {
                console.log(`values[${thisIndex}] = ${JSON.stringify(val)}`);
                values[thisIndex] = val; // <=== Notice use of `thisIndex`
                --remainingElementsCount;
                if (remainingElementsCount === 0) {
                    resolve(values);
                }
            }).catch(reject);
            ++remainingElementsCount;
            ++index;
        }
        --remainingElementsCount;
        if (remainingElementsCount === 0) {
            resolve(values);
        }
    });
}

// Pass in an iterable of promises, raw values, and thenables
fakeAll([
    randomDelay("one"),
    "two",                                      // Raw value
    randomDelay("three"),
    {then(cb) { cb("four"); }},                 // Synchronous thenable
    {then(cb) { setTimeout(cb, 20, "five"); }}, // Asynchronous thenable
])
.then(results => {
    console.log(results);
})
.catch(error => {
    console.error(error);
});
.as-console-wrapper {
  max-height: 100% !important;
}

Is this a special feature with axios? Thank you.

No, it's defined by the specification for Promise.all.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875