회사 업무 중, 특정 정보를 수정해야 하는 페이지를 구현해야 했다. 해당 화면은 크게 “데이터 제공자 정보”, “기존 계약 정보”, “새롭게 추가할 계약 정보” 이렇게 세가지 정보들이 보여진다.
그리고 이에 맞게 정보 수정 API도 각각 3개로 분리되어 있었다.
그런데 곰곰히 생각해보니, 각 요청을 안전하게 처리할 수 있는 (프로시저 같은) 기능이 없다면, 요청을 보내는 순서와 타이밍에 따라 DB 값이 꼬일 수 있겠다는 생각이 들었다. (만약 데이터 제공자 정보를 갱신하기 전에 그에 딸린 기존 계약 정보가 갱신되다면? 혹은 동시다발적으로 요청이 간다면? 먼저 온 요청 처리 중 이후에 온 요청이 간섭한다면? 등등..)
그래서 BE 담당자분에게 물어봤더니, API 요청을 되도록이면 순차적으로 보내주셨으면 좋겠다는 얘기를 해주셨다.
우선 Promise의 특성에 대해 잘 알고 있어야 한다.
console.log(1);
const p = new Promise((resolve) => {
console.log(2);
setTimeout(() => resolve("done"), 1000);
});
console.log(3);
p.then(result => console.log(result));
console.log(4);
/* 로그 순서
1
2
3
4
(1초 후)done
*/
여기서 주의할 점이, Promise 인스턴스를 만드는 시점 자체는 지연되지 않는다는 점이다. 지연 평가(lazy evaluation)이라는 단어에서 알 수 있듯이, 값을 미리 구해 놓고 필요할 때 가져오기 때문이다.
// 데이터 제공자 정보 수정 프로미스
const mutateDataProviderInfo = _mutateDataProviderInfo(...);
// 기존 계약 수정 & 새로운 계약 추가 프로미스 배열
const mutateContractList = contractList.map((contract) =>
contract._isExisting === true
? mutateExistingContract(...) // 기존 계약인 경우
: mutateNewContract(...) // 새 계약인 경우
);
// 이제 API 요청을 순차적으로 보내는 것 같지만,
// 이미 이 라인이 실행될 시점에는 API 요청이 다 전송된 상태다.
const mutateList = [mutateDataProviderInfo, ...mutateContractList];
for (const p of mutateList) { ... }
그래서 react-query를 이용한 mutate 함수를 호출하여 promise 객체를 미리 받아 놓으면, API 요청들은 전혀 순차실행되지 않는다.
그래서 나는 mutate 함수를 호출하는 래핑 함수를 구현해서 문제를 해결하였다.
// 데이터 제공자 정보 수정 래퍼 함수
const mutateDataProvider = () => _mutateDataProviderInfo(...);
// 기존 계약 수정 & 새로운 계약 추가 래퍼 함수 배열
const mutateContractList = contractList.map((contract) =>
() => contract._isExisting === true
? mutateExistingContract(...) // 기존 계약인 경우
: mutateNewContract(...) // 새 계약인 경우
);
const resultList = [];
for (const mutateFn of [mutateDataProvider, ...mutateContractList]) {
try {
// 해당 promise 가 fulfill 될 때 까지 기다린다
const result = await mutateFn();
resultList.push(result);
} catch (e) {
resultList.push(e);
}
}
이렇게하면 래퍼 함수가 호출되기 전까지는 API 요청 함수 자체가 실행되지 않으니, 순차 API 요청을 보낼 수 있게 된다.
덧.
[다른 동료에게 도움이 된 것 같아 뿌듯하다.]