본문 바로가기
프로그래밍 언어/JavaScript

[js] forEach와 비동기 작업(Transaction API error: Transaction already closed: Could not perform operation 발생!)

by BK0625 2024. 5. 31.
반응형

 

외주 작업을 하던 도중에 

 

Transaction API error: Transaction already closed: Could not perform operation

 

라는 에러를 만났다.

 

해당 코드는 다음과 같은데

 

   	await this.prisma.$transaction(async (tx) => {
                const maxCombineNum = await tx.order.aggregate({
                   ...
                });

				//원하는 데이터에서 +1
                let newCombineNum = maxCombineNum._max.combineNum + 1;

                //위에 newCombineNum과 추가적인 데이터 업데이트
                await tx.order.updateMany({
                    ...
                });

                //업데이트할 환자들 데이터 찾기
                const patients = await tx.order.findMany({
                   ...
                });
                
               //반복문 돌면서 업데이트
                patients.forEach(async e => {
                    await tx.patient.update({
                        ...
                    });
                });
            });

 

 

transaction 안에서 이뤄지는 작업이다. 먼저 제일 큰 combineNum을 찾고 새 데이터 입력을 위해 +1을 해준 뒤 order 테이블을 업데이트 하고 patient 테이블도 업데이트 해야 해 업데이트를 할 환자들 데이터를 찾아 반복문을 돌면서 patient 테이블을 돌면서 업데이트 처리를 하는 간단한 로직.

 

 

생각없이 반복문으로 forEach문을 선택해서 돌려봤더니 위 에러가 나온 것이다.

 

 

알아보니 forEach문 내부의 비동기 작업이 문제였다. 위 에러는 transaction이 이미 종료된 상태에서 작업을 시도할 때 발생하는 에러 메시지이다. forEach는 비동기적으로 동작하지  않는다. 따라서 트랜잭션이 끝나기 전에 비동기 작업이 완료되지 않을 수 있는 것이다.

 

 

자바스크립트에서 forEach는 비동기적으로 동작하지 않는다. 이는 forEach 메서드가 콜백함수를 동기적으로 실행하기 때문이다. 따라서 콜백 함수가 비동기 작업을 포함하더라도 forEach는 그 작업이 완료될 때까지 기다리지 않고 다음 요소로 넘어가 버리는 것이다. 

 

 

예시 코드를 봐보자

 

const arr = [1, 2, 3];

arr.forEach(async (num) => {
    await someAsyncFunction(num);
});

console.log('End');

 

 

forEach는 배열의 각 요소에 대해 제공된 콜백 함수를 실행하는 메소드이다. 이는 동기적으로 작동하게 된다. 이는 콜백 함수가 비동기 작업을 포함하더라도 forEach 자체는 비동기적으로 기다리지 않고 다음 요소로 즉시 넘어가는 것을 의미하는 것이다..

 

 

위 코드에서 someAsyncFunction은 비동기 함수이며 각 요소에 대해 비동기적으로 호출된다. 그러나 forEach 메소드는 각 요소에 대한 비동기 함수 호출이 완료될 때까지 기다리지 않고 즉시 다음 요소로 넘어가게 된다. 따라서 End가 먼저 출력되어 버린다. 이렇게 forEach 메소드가 동기적으로 작동하기 때문에 발생한 문제였다.

 

 

따라서 문제 해결을 위해 forEach를 for ...of 루프로 변경하였다. 이렇게 되면 모든 비동기 작업이 순차적으로 처리되게 된다. 비동기 작업을 순차적으로 사용하기 위해서는 forEach 대신 for...of나 for 문을 사용하여야 한다.

 

 // for...of 루프를 사용하여 비동기 작업을 순차적으로 처리
 for (const e of patients) {
 	await tx.patient.update({
       ...
    });
 };
반응형