개발 기술/사소하지만 놓치기 쉬운 개발 지식

[JS/Eslint] 레이스컨디션을 유발하는 await는 쓰지말자(require-atomic-updates)

by GicoMomg (Lux) 2022. 9. 3.

이번 시간에는 eslint 룰 중 하나인, require-atomic-updates에 대해 알아보았다!


1. eslint룰, require-atomic-updates

1) 이 규칙은?

  • await가 레이스 컨디션을 유발할 경우, await 사용을 금지하는 규칙이다.

2) await가 레이스 컨디션을 유발하는 경우

  • 아래 예시는 personage 데이터를 가져와, age의 총합을 구하는 로직이다.
const PEOPLE = [{ id: 1, age: 12 }, { id: 2, age: 22 }];
let totalAge = 0;

// await를 사용해 age의 총합을 구하는 함수
async function addAge(id) {
  totalAge += await getAge(id);          // 주요하게 볼 부분
}

// id에 해당하는 age를 리턴하는 함수
async function getAge(id) {
  const randomTime = Math.random() * 1000;
  await waiting(randomTime);           
  return PEOPLE.find((person) => person.id === id).age;
}

// 비동기 역할을 하는 함수
async function waiting(time) {   
  setTimeout(() => {
    Promise.resolve('End waiting');
  }, time);
}
// age총합 구하는 로직
await Promise.all([addAge(1), addAge(2)]);  // 병렬로 실행
console.log('total age:', totalAge);

😯 totalAge의 결과는 무엇일까? 신기하게도 34가 아닌 22가 나온다! 이유는 무엇일까?

  • 함수 실행 순서와 함께 이유를 알아보자!
async function addAge(id) {
  totalAge += await getAge(id);            // 버그 발생 지점!
}

await Promise.all([addAge(1), addAge(2)]); // 함수 실행
console.log('total age:', totalAge);
함수 실행 순서  설명
addAge(1) 실행  getAge(1)에서 totalAge의 초기값을 읽는다. (이때 초기값은 0)
 getAge(1)에서 Promise가 완료될 때까지 기다린다.
addAge(2) 실행  Promise.all은 병렬 실행하므로,
 getAge(1)의 Promise 결과를 기다리는 동안 getAge(2)가 실행한다.
 getAge(1)이 완료되지 않은 상태에서 getAge(2)가 실행되므로,
 totalAge의 초기값이 0인 상태에서 변수를 업데이트하여 결과값이 22가 나온다.

그렇다면 어떻게 하면 레이스컨디션을 피할 수 있을까? 2가지 방법을 살펴보자!


3) await의 레이스컨디션을 피하는 방법

(1) await가 끝나면 데이터 업데이트하기

  • getAge(id)Promise가 끝난 뒤 age값을 받아, totalAge의 값을 업데이트하는 방법이다.
async function addAge(id) {
  const age = await getAge(id);    // 변경
  totalAge += age;
}

(2) 변수 참조하지 않기

  • await로 인해 레이스컨디션이 발생한 이유는, 참조 변수값을 업데이트할 때 이전의 Promise가 끝나기 전에 다른 Promise를 실행했기 때문이다.
  • 그러므로 아래와 같이, 지역 변수를 선언하여 값을 계산해주면 레이스컨디션을 피할 수 있다!
Promise.all([getAge(1), getAge(2)]).then(ages => {
  const totalAge = ages.reduce((acc, age) => acc + age, 0);
  console.log(totalAge);
});

반응형

댓글