[이슈] 배열에 대한 비동기 처리 방법

2021. 1. 28. 12:27프로그래밍-Web/Node.js

Promise함수는 자바스크립트의 특징을 가장 잘 나타내는 함수인데, 요새는 async-await 구문을 이용하여 더욱 간단하게 처리하는 경우가 많다.

그러나 async-await구문은 배열을 인자로 받을 수는 없기 때문에, 배열 비동기 처리는 조금 더 고민이 필요하다.

 

예시는 arr이라는 배열에 대해 testFunction이라는 로직을 순차적으로 처리해 messages라는 새로운 배열을 만드는 것이다

직관적으로는 key마다 testFunction이 결과를 리턴해줄때까지 기다리고 messages를 만들것이라 추측할것이다

 

messages 결과: [ Promise { <pending> }, Promise { <pending> } ]

 

그러나 실제로는 pending하는 요소만 리턴하게 된다

왜냐하면 map은 각 원소가 처리될때까지 기다리지 않기 때문이다

testFunction이 리턴을 해주든말든 다음 key로 넘어간다

따라서 배열이 각 key에 대해 결과를 받을때까지 기다리도록 적절하게 처리해야 한다

여러가지 방법이 있고 모두 장단이 다르다

 

0) map만 사용하는 경우 : pending 리턴

 

1) Promise.all + map

 

map의 결과를 바로 변수에 담지 않고, Promise.all로 기다렸다가 담는다.

Promise.all은 arr의 map에 대해 모두 병렬처리로 만들어준다.

결과를 기다리지 않고 key를 계속 던지는 것이다.

 

단, 여기에서 주의할 점은 Promise.all 은 순서가 보장되지 않는다는 점이다

새로운 배열의 원소들은 끝나는 순서대로 리턴된다

즉, 동시에 key들을 로직에 돌려놓고 먼저 끝나는 애들부터 차곡차곡 Promise.all이 쌓았다가 모두가 끝나면 messages에 담는다

이때 하나라도 실패할 경우, 바로 로직실패처리를 띄우게 된다는 특징이 있다.

 

2) foreach 

 

for는 리턴을 해주지 않는이상 아무것도 리턴하지 않는다

따라서 그 무엇도 pending하지 않는다

반드시 리턴을 해주어야 새로운 배열이 생긴다

배열을 비동기 처리 할때 forEach의 사용은 가능한 피하는것이 좋다

 

 

3) for ~ of

 

성능문제가 없다면 for ~of 구문으로 처리하는 것도 방법이다

다만 이 경우는 promise.all처럼 병렬로 처리하지 않기 때문에 key에 대한 로직 각각이 끝날때까지 순회를 기다린다

또한 map처럼 새로운 배열이 만들어지는 것은 아니므로, 따로 배열을 만들어 푸쉬해주어야 한다.

 

4) reduce

 

병렬처리도 하고 싶고, 순서도 보장되게 하고 싶다면 reduce가 최선의 방법이다

reduce는 내가 원하는 value를 정해서 다음 순회로 넘겨줄 수 있다는 특징을 가진다.

따라서 Promise의 resolve를 모든 순회마다 상수로 넘겨준다

 

reduce는 previousPromise라는 argument가 전달되면 다음 순회를 진행한다.

그리고 Promise.resolve라는 성공메소드를 argument로 보내줄 수 있다.

그렇기 때문에 previousPromise는 즉시에 결과를 리턴하게 되고 바로 다음 순회로 넘어간다

하지만 nextData는 newElem이라는 변수가 채워질떄까지, 즉 testFunction이 끝날때까지 기다린다

이는 곧 arr의 모든 key들이 순서대로 순회를 명령받은 상태에서(병렬처리),

newElem 역시 순서대로 채워진다는 뜻이 된다.

 

promise.all이 병렬처리를 명령받지만, 리턴값을 원래 순서대로 채우지 않고 빨리 끝나는 순서대로 채우는데 반해

reduce는 병렬처리와 더불어, newElem에 들어가는 key 순서대로 결과값이 나오게 된다.

 

 

간략하게 표로 정리해보면 다음과 같다

 

 

병렬처리를 하는가(in parallel)

=한번의 순회마다 명령만 하고 루프를 빠져나가는가?

결과 순서를지키는가

결과를 기다리는가=바로콘솔이 찍히는가?

결과

await + map

o(결과를 안기다리고 바로 돌아가니까)


그래서 얘네는 바로 결과 리턴하면 Promise<pending>을 리턴함

x

x

몇몇 원소는 pending리턴, await는 promise배열은 결과 안기다림

promise.all+map

o(원리는 기본적인 병렬처리와 같다)

x

o(모든 배열이 다 찰때까지 기다림) 

병렬처리되나 순서를 안지킴

await +foreach

o(결과를 안기다리고 바로 돌아가니까)


얜 pending도 안함. 

foreach 자체가 리턴이란걸 안해서.

x

x

결과에 대한 비동기 로직 안기다림. foreach는 내부 await 신경안쓰고 그냥 끝나면 다음 순회 

for of(in)

x

o

o

for문 쓰는 함수 자체를 async로 만들고 각 결과를 await하게 함(가장 흔함)

reduce

o

o

reduce자체는 동기니까 순서대로 담김. 단, promise만 리턴해서 병렬처리만 되도록 해주는 것

o

완벽