Search code examples
javascriptes6-promise

Promises: keep track of matching input/output identifiers


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?


Solution

  • 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...
    });