0

Say I need to fire several requests via an API, in order to store some data in a database. Every entry has a unique identifier on the client side. However, upon insertion on the remote side, it will get a new unique identifier. This cannot be changed, i.e. I cannot force the remote side to use the same identifiers. This identifier (along with some other data) is sent back to the client when the Promise resolves. What is the best practice to keep track of it all.

Illustration:

let inputValues = ["id1", "id2", "id3"];
for (val of inputValues) {
   api.insert(val).then( (result) => {
      console.log("new id:", result.id);
   });
}

In the end, I imagine having maybe an associative array like

   [ "id1": "new ID for id1 from remote server",
     "id2": "new ID for id2 from remote server",
     "id3": "new ID for id3 from remote server" ]

I am pretty confident that I could write up something that would do the job, but that would probably be awful code full of anti-patterns. So I prefer to ask first: what would be the recommended way to do it?

Philipp Imhof
  • 204
  • 1
  • 7
  • `Array.prototype.map()` + `Promise.all()` -> [How much research effort is expected of Stack Overflow users?](https://meta.stackoverflow.com/questions/261592/how-much-research-effort-is-expected-of-stack-overflow-users) – Andreas Jan 25 '22 at 11:34
  • Thanks. I actually thought about using `Promise.allSettled()`, but -- as I said -- I am not sure whether my approach would be a good one. I agree, however, that I could have mentioned that in my question already. – Philipp Imhof Jan 25 '22 at 11:47

1 Answers1

1

It looks like you're doing your updates in parallel (rather than in series), so you could use Promise.allSettled, which accepts an iterable (like an array) of promises, waits for all of them to settle (get fulfilled or rejected), and then returns an array in the same order as the iterable you provided to it. You can then loop through and, for the successful updates, apply the new ID.

Something like this (in an async function):

const inputValues = ["id1", "id2", "id3"];
const results = await Promise.allSettled(
    inputValues.map(value => api.insert(value))
);
// Here, `results` and `inputValues` will be parallel arrays
for (let i = 0; i < results.length; ++i) {
    const result = results[i];
    if (result.status === "fulfilled") {
        const newId = result.value.id;
        // Successful update, `newId` is the new ID for `inputValues[i]`
    }
}

Here's an example, with the promises intentionally being settled out of order to demonstrate that the result array is in the same order as the input iterable (since you weren't sure that was the case):

const api = {
    async insert(value) {
        const delay = value === "id2" ? 1000 : 200;
        await new Promise(resolve => setTimeout(resolve, delay));
        console.log(`Fulfilling ${JSON.stringify(value)}`);
        return {
            id: `New ID for ${value}`
        };
    }
};
(async () => {
    const inputValues = ["id1", "id2", "id3"];
    const results = await Promise.allSettled(
        inputValues.map(value => api.insert(value))
    );
    // Here, `results` and `inputValues` will be parallel arrays
    for (let i = 0; i < results.length; ++i) {
        const result = results[i];
        if (result.status === "fulfilled") {
            const newId = result.value.id;
            const input = inputValues[i];
            console.log(`input value = ${JSON.stringify(input)}, newId = ${JSON.stringify(newId)}`);
        }
    }
})();

In that you can see that even though the operation for "id2" took longer than the ones for "id1" and "id3", it's still in the second position in the result.


If for some reason you can't use an async function:

const inputValues = ["id1", "id2", "id3"];
Promise.allSettled(
    inputValues.map(value => api.insert(value))
)
.then(results => {
    // Here, `results` and `inputValues` will be parallel arrays
    for (let i = 0; i < results.length; ++i) {
        const result = results[i];
        if (result.status === "fulfilled") {
            const newId = result.value.id;
            // Successful update, `newId` is the new ID for `inputValues[i]`
        }
    }
})
.catch(error => {
    // ...handle/report error...
});
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks. I thought about `allSettled` but was not sure about the order. I read something about `Promises.all` being guaranteed to keep the order, but I somehow had different results with `allSettled` and (artificial) promises (resolving after a random amount of time). But with your advice I know now that there must have been some other problem. – Philipp Imhof Jan 25 '22 at 11:52
  • @PhilippImhof - Both are guaranteed to provide the result array in the same order a the iterable you provide them. If you use `Promise.allSettled([a, b, c])`, the result array will have the results in `a`, `b`, `c` order, regardless of the order in which the promises settled. – T.J. Crowder Jan 25 '22 at 12:40
  • Thanks. And meanwhile, I found out what my problem was: I stored the promises in an associative array with the ID as the key and relied on the output of `console.log` in Chrome. However, when expanding the array (clicking on the triangle), the output was magically sorted in alphabetical order. The collapsed form, which I did not watch closely enough, was in the right order. So all that confusion was just .... for no reason. – Philipp Imhof Jan 25 '22 at 12:53
  • I opened another question here: https://stackoverflow.com/questions/70850355/javascript-console-output-of-associative-array because I find that issue somehow intriguing. – Philipp Imhof Jan 25 '22 at 14:27