아 그거 뭐였지

[JavaScript] Promise.all로 비동기로직 병렬처리하기 (.feat Promise.allSettled) 본문

Front-End

[JavaScript] Promise.all로 비동기로직 병렬처리하기 (.feat Promise.allSettled)

승발자 2023. 2. 13. 23:42
728x90
반응형

Promise.all 적용하기 전

 

스마트스토어 관리툴을 제작하고있는 업무를 맡던도중 상품수정에 관련한 업무를 하고있었다.

 

회사 스마트스토어가 4개가 있는데 각 스토어에 중복적으로 상품들이 업로드되어있다.

같은 상품들이 업로드되어있다보니 하나의 상품을 수정하면 다른 스토어에서의 해당 상품이 수정되어야하는

귀찮은 일이 발생한것이다.

 

처음에는 for문으로 스토어 갯수별로 반복문을 돌아서 해결했지만,

하나의 스토어 수정이 끝나기 전까지는 다른 스토어 수정이 불가했었다.

 

스토어 하나당 1초가 걸린다고 가정했을때 모든 스토어를 수정하기위해서는 4초가 걸리는것이다.

상품 하나 수정하는데 4초나 걸린다니 사용자 입장에서는 답답해서 다시는 사용하지 않을것이다.

 

비동기함수 내에 반복되는 작업들이 병렬적으로 처리되기를 바라고있었지만, 동기적으로 실행이 되고있었다.

 

어쩌면 당연했다. await 를 사용했기때문에 해당 스토어 상품이 수정이 완료되기 전까지는 다른 스토어의 상품 수정이

시작되지 않았으니 말이다. 

const storeArr = ['A','B','C','D']
const productId = "product"

const updateStore = async ()=>{
    for(let store of storeArr){
    	//상품 수정하는 비동기함수 A 스토어를 끝내야 B로 넘어감
    	await updateProduct(store);
	}	
}

이를 해결하기위해서는 비동기함수를 동시에 실행시키는 무언가가 필요했다.

 

그 무언가가 이번에 사용해볼 Promise.all 이다. 

 

Promise.all

Promise.all을 간략히 설명하고 가자면 여러개의 Promise를 동시에 실행시키고 그 결과를 Promise배열로 반환받는다.

여기서 동시에 실행시킨다는 점에서 본인이 원하는 기능을 만들수있을것이다. 단 Promise.all은 순서를 보장해주지않는다는 점을 주의해야한다.

 

작업 특성상 스토어의 수정 순서는 상관이 없었기때문에 Promise.all을 사용해서 처리하기로했다.

문법은 간단하다 Promise.all() 파라미터로 프로미스 반환배열을 넣어주면된다.

Promise.all이 끝난뒤에 실행할 작업이있다면 .then 메소드로 실행할 작업들을 정의해주면된다. 에러는 catch로 처리한다.

Promise.all([...프로미스 반환배열])
.then(()=>"Promise.all 끝난뒤 실행할것들")
.catch((e)=>console.log(e))

 

Promise.all로 작성된 코드는 다음과같다.

const updateStore = async()=>{
	try{
            //스토어 상품수정 동시에 하는 함수
            await Promise.all(storeArr.map(async(store)=>{
                await updateProduct({store,id});
            }))
            .then(async ()=>{
                //Promise.all실행이 완료되면 실행되는 로직
	        await db.query(`update naver_products set price=${price} where sellerId=${sellerId}`)
                res.status(200).json({msg:"수정이 완료되었습니다."});
        }).catch((e)=>{throw new Error(e)});
    }
    catch(e){
        console.log(e);
        res.status(400).json({msg:"수정이 실패하였습니다."});
    }
}

이렇게 Promise.all로 작성하니 엄청나게 시간이 단축된것을 체감할수있었다.

그도 그럴것이 기존의 방식이라면 스토어 하나당 수정시간을 1초로 가정하면 1초 * 스토어개수 의 시간이 나오는데

Promise.all로 처리하면 동시에 실행이 되버리니 편하게 생각하면 스토어개수만큼의 시간은 벌지 않았나 생각이된다.

 

 

한가지 더 알아두어야 할 점은 Promise.all은 중간에 에러가나면 완료되지못하고 멈춰버린다...

A,B,C 중에서 A와B는 완료되었는데 C에서 에러가나면 A,B는 반환되지못한다. 따라서 Promise.all은 전체의 성공을 보장받을 때 사용하는것이 좋을듯하다.

 

본인은 중간에 에러가 나도 Promise.all을 그대로 진행시키고 싶어서 Promise.allSettled를 사용했다.

 

Promise.allSettled

사용법은 Promise.all과 똑같지만 차이점이라면 진행도중 에러가 나도 진행이 된다는 점과

.then 메소드를 사용했을때 반환되는 값이 key value 객체이다.

성공적으로 반환된 Promise라면 {status:"fulfilled",value:"값"}

에러가나서 실패된 Promise라면 {status:"rejected",reason:"값"}

이렇게 두 타입으로 나뉘어서 반환된다. status를 구분해서 가져오고 싶은 값들을 가져오면 된다.

완성된 코드는 아래와같다.

const updateStore = async()=>{
	try{
            //스토어 상품수정 동시에 하는 함수
            await Promise.allSettled(storeArr.map(async(store)=>{
                await updateProduct({store,id});
            }))
            .then(async (result)=>{
                //성공 완료한 Promise만 빼오고 상점이름들을 가져오는 로직
            	const fulfilledStore = result.filter(store=>store.status==="fulfilled").map(_store=>store.value).join(',');
                //Promise.all실행이 완료되면 실행되는 로직
	        await db.query(`update naver_products set price=${price} where sellerId=${sellerId}`)
                res.status(200).json({msg:`${fulfilledStore} 수정이 완료되었습니다.`});
        }).catch((e)=>{throw new Error(e)});
    }
    catch(e){
        console.log(e);
        res.status(400).json({msg:"수정이 실패하였습니다."});
    }
}

 

 

 

습관성 async await을 사용하다보니 비동기함수의 본질을 잊어버린것같아서 부끄러웠던 문제해결이였다.

코드 한줄을 작성하더라도 생각을 하고 작성하는 습관을 기르도록 하자.

 

참고링크

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

728x90
반응형
Comments