javascriptasynchronousasync-awaiteslintrace-condition

Why is setting an object property asynchronously based on a different property of the same object a race condition?


I have the following code, which ES Lint marks as a race condition:

const fillPersonPositions = async (person) => {
    person.positions = await getPositions(person.id);
};

Full error:

Possible race condition: person.positions might be reassigned based on an outdated value of person.positions.eslint(require-atomic-updates)

If I change person.id to something unrelated to the person object like null, the error vanishes.

What is the problem here and how to avoid it? (I mean fix code if it's a real race condition problem or tell the linter that it's not a problem if not).


Solution

  • This code could be problematic if you called fillPersonPositions more than once, and the id of the person changed in the meantime. Eg:

    person.id = 1;
    const p1 = fillPersonPositions(person);
    person.id = 3;
    const p2 = fillPersonPositions(person);
    await Promise.all([p1, p2]);
    

    Here, there's no guarantee that the first fillPersonPositions resolves first, so at the end, you might end up with person having an ID of 3, but a positions property corresponding to the ID of 1.

    Of course, the above is very contrived, but ESLint does not know that.

    One possible fix would be to avoid mutation, and instead return (and use) a new object:

    const fillPersonPositions = async (person) => {
        const positions = await getPositions(person.id);
        return { ...person, positions };
    };
    

    (It's often a good idea to avoid mutation when possible anyway, for the sake of more understandable code - for similar reasons, const is a better choice than let when you have the option)