2025. 1. 23. 22:21ㆍ프로그래밍/JavaScript
개요
순회작업에서 비동기 작업을 처리하는 과정에서 더 깊게 이해하게 된 Promise.all의 동작과 Javascript의 병렬처리 여부에 관해 정리해보고자 한다.
상황
여러 요소가 있고 각 요소들에 대해 비동기 처리를 하려면 어떻게 해야할까?
MDN을 참고해보면 forEach는 기본적으로 콜백함수를 기다리지 않는다고 명세돼있듯이 forEach내부에서 await를 걸어도 forEach문은 끝나게 된다. 콜백함수로 전달받은 인수인 함수를 실행만 하는 것이다.
다음의 간단한 예제를 살펴보자
예제1. forEach의 콜백함수를 비동기(async)로 선언한 경우
async function promise1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('hi');
resolve('success');
}, 1000);
});
}
[1, 2].forEach(async element => {
console.log(element);
await promise1();
});
console.log('finish');
// 콘솔 출력 결과
// 1
// 2
// finish
// hi (1초뒤 출력)
// hi (1초뒤 출력)
위와 같이 forEach의 콜백함수에 async를 명세하고 내부적으로 await를 선언해도 forEach는 이미 실행된 이후에 finish가 출력되게 된다. 만약 forEach아래에서 처리 결과에 따라 동기적으로 수행해야 하는 로직이 있다면 예상치 못한 동작이 일어날 것이다.
위 동작은 map 함수도 마찬가지로 동작한다.
이를 해결하기 위해서는 Promise.all 함수를 사용해야 한다.
그 전에 알아야할 것은 forEach와 map의 큰 차이점 중 하나는 바로 자기 자신을 배열로써 반환하냐 안하냐는 것이다.
const arr = [1, 2];
const result = arr.forEach(element => {
return element * 2;
});
console.log(arr);
console.log(result);
const arr2 = [1, 2];
const result2 = arr2.map(element => {
return element * 2;
});
console.log(arr2);
console.log(result2);
알다시피 arr2만 각 요소들의 return된 값으로 이루어진 배열을 넘겨받게 된다.
이를 활용하면 map함수의 콜백함수들을 promise.all의 인수로 넘겨줌으로써 비동기적인 작업도 동기적으로 처리할 수 있게 된다.
이를 확인하기 위해서는 단순히 map 함수 내부에서 비동기 함수를 호출한 후 해당 map 함수 자체를 콘솔에 출력해보면 알 수 있다.
예제2. map의 콜백함수의 return값 확인
async function promise1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('hi');
resolve('success');
}, 1000);
});
}
console.log(
[1, 2, 3].map(element => {
return promise1();
})
);
// 출력결과 [ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
즉 Promise.all은 배열로 이루어진 promise 요소들이 모두 끝날때까지 기다리는 것이다.
또한 Promise.all은 promise 중 하나라도 reject, 즉 throw가 발생하게 되면 다른 Promise 작업에 reject 호출이 전이되는 특징도 가지고 있다.
따라서 이를 활용하여 원하는 대로 동작하게 하려면 아래 코드와 같이 작성하면 될 것이다.
예제3. Promise.all을 활용한 병렬 처리
async function test() {
await Promise.all(
[1, 2].map(async element => {
await promise1();
})
);
}
test().then(() => {
console.log('finish');
});
여담
또한 자바스크립트가 병렬처리를 지원하는지에 대한 의문이 들 수 있을 것이다. 왜냐하면 공식적으로 비동기 문법을 지원하기 때문이다.
그러나 결론적으로 말하자면 자바스크립트는 싱글스레드라 병렬 처리 하는 것 처럼 보이는 것 뿐인 싱글스레드이다.
그럼에도 다음과 같은 코드들은 분명한 차이가 있을 것이다.
async function sayHi() {
return new Promise(resolve => {
setTimeout(() => {
console.log('hi');
resolve('success');
}, 1000);
});
}
async function sayBye() {
return new Promise(resolve => {
setTimeout(() => {
console.log('bye');
resolve('success');
}, 1000);
});
}
async function test() {
await sayHi();
await sayHi();
await sayHi();
}
async function test2() {
await Promise.all([sayBye(), sayBye(), sayBye()]);
}
test();
test2();
// # console
// hi (1초후)
// bye (1초후)
// bye (1초후)
// bye (1초후)
// hi (2초후)
// hi (3초후)
실제로 자바스크립트가 싱글스레드임에도 병렬처리 하는것처럼 보이는 이유는 비동기 작업에 resource를 할애하지 않아도 되는 경우 이벤트 루프에 삽입된 다른 이벤트들을 순간적으로 계속 처리해내기 때문이다. 그렇기 때문에 병렬처리라는 단어의 정의를 멀티스레드를 기준으로 한다면 틀린 말이지만 위와 같이 리소스를 분담해서 처리한다는 측면에서는 병렬처리라고 할 수 있을 것이다.
'프로그래밍 > JavaScript' 카테고리의 다른 글
encodeURI와 encodeURIComponent의 차이점 (2) | 2024.09.04 |
---|---|
[ Javascript ] 함수 호이스팅, 함수 선언문과 표현식, 함수 리터럴 (0) | 2024.07.28 |
[ Javascript ] 변수 및 객체가 복사 및 할당되는 원리 & 얕은 복사와 깊은 복사 (2) | 2024.07.25 |
[ Javascript ] 암묵적 캐스팅과 산술 연산자 +의 문자열 형변환 (0) | 2024.07.19 |
[ Javascript ] Javascript의 변수 선언과 초기화 동작 원리 및 숫자(number) 타입의 크기 (0) | 2024.07.18 |