[JavaScript] 자바스크립트 비동기 처리
비동기 프로그래밍의 개념
비동기적
일반적으로 주어진 프로그램의 코드는 한 번에 한 가지만 발생하면서 곧바로 실행된다. 함수가 다른 함수의 결과에 의존하는 경우 다른 함수가 완료되고 반환될 때까지 기다려야하므로, 전체 프로그램이 중지된다. (동기적)
Blocking Code
비동기 기술은 특히 웹 프로그래밍에서 매우 유용하다. 웹 앱이 브라우저에서 실행되고 브라우저에서 제어를 반환하지 않고 큰 크기의 코드를 실행하면 브라우저가 정지된 것처럼 보일 수 있다. 이것을 Blocking이라고 한다. 브라우저는 웹 앱이 프로세서의 제어를 반환할 때까지 계속해서 사용자 입력을 처리하고 다른 작업을 수행하지 못하도록 차단된다.
스레드
스레드는 기본적으로 프로그램이 작업을 완료하는데 사용할 수 있는 하나의 과정으로, 각 스레드는 한 번에 하나의 작업만 수행할 수 있다.
단일 스레드 JavaScript
JavaScrip는 단일 스레드로, 코어가 여러 개 있어도 메인 스레드라고 하는 단일 스레드에서만 실행할 수 있다.
비동기 자바스크립트(Asynchronous JavaScript)
동기 자바스크립트(Synchronous JavaScript)
비동기 자바스크립트를 이해하기 위해서 우선 동기 자바스크립트가 무엇인지부터 이해해야 한다.
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
alert('You clicked me!');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
위의 예시에서 행은 차례로 실행된다.
- <button> DOM에서 이미 사용 가능한 요소에 대한 참조를 가져온다.
- click 버튼을 클릭할 때 다음을 수행하도록 이벤트 리스너를 추가한다.
- alert() 메시지가 나타난다.
- 경고가 해제되면 <p> 요소를 만든다.
- 텍스트 콘텐츠를 설정한다.
- document에 해당 p 요소를 추가한다.
각 작업이 처리되는 동안 아무 일도 일어나지 않으며 렌더링이 일시 중지된다. (JsavaScript는 단일 스레드이며, 단일 메인 스레드에서 한 번에 한 가지 업무만 처리가 가능해 다른 작업들은 현재 작업이 완료될 때까지 blocking되기 때문이다.)
비동기 자바스크립트(Asynchronous JavaScript)
네트워크에서 파일 가져오기, 데이터베이스 액세스 및 데이터 반환, 웹 캠에서 비디오 스트림 액세스, 디스플레이를 VR 헤드셋으로 브로드캐스트 등 많은 API 기능들은 비동기 코드를 사용하여 실행된다.
let response = fetch('myImage.png'); // fetch is asynchronous
let blob = response.blob();
// display your image blob in the UI somehow
위의 예시에서 이미지를 다운로드 하는데 시간이 얼마나 걸릴지 알 수 없기 때문에, 두번째 줄을 실행하려고 할 때, response를 아직 사용할 수 없어 오류가 발생한다.
JavaScript에서 자주 사용하는 비동기 코드 스타일 유형으로 callback과, promise가 있다.
Callback
비동기 콜백은 백그라운드에서 코드 실행을 시작할 함수를 호출할 때 인수로 지정되는 함수이다. 백 그라운드 코드 실행이 완료되면 콜백 함수를 호출하여 작업이 완료되었음을 알릴 수 있다. 콜백을 사용하는 것은 약간 구식이지만 여전히 일반적인 API에서 많이 사용된다.
비동기 콜백의 예는 addEventListener() 매서드의 두 번째 매개변수 이다.
btn.addEventListener('click', () => {
alert('You clicked me!');
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
첫 번째 매개변수는 수신할 이벤트의 유형이고 두 번째 매개변수는 이벤트가 발생할 때 호출되는 콜백 함수이다.
콜백 함수를 다른 함수에 대한 인수로 전달할 때 함수의 참조를 인수로 전달할 뿐이다. 즉, 콜백 함수가 즉시 실행되지 않는다.
포함하는 함수의 본문 내부 어딘가에 비동기적으로 콜백된다.
콜백은 다목적으로 사용된다. 함수가 실행되는 순서와 함수 사이에 전달되는 데이털르 제어할 수 있을 뿐망 아니라 상황에 따라 다른 함수에 데이터를 전달할 수도 있다.
모든 콜백이 비동기식인 것은 아니며 일부는 동기식으로 실행된다. 예를 들어 Array.prototype.forEach()의 콜백은 아무 것도 기다리지 않고 즉시 실행된다.
Promise
프로미스는 최신 Web API은 최신 Web API에서 사용되는 새로운 스타일의 비동기 코드이다.
fetch('products.json').then(function(response) {
return response.json();
}).then(function(json) {
let products = json;
initialize(products);
}).catch(function(err) {
console.log('Fetch problem: ' + err.message);
});
여기서 fetch()는 단일 매개변수(네트워크에서 가져오려는 리소스의 URP)을 취하고 프로미스를 반환한다. 프로미스는 비동기 작업의 완료 또는 실패를 나타내는 개체로 말 그대로 중간 상태를 나타낸다.
- 위 코드의 두 then 블록 : 둘 다 이전 작업이 성공한 경우 실행할 콜백 함수를 포함하고 있으며 각 콜백은 이전에 성공한 작업의 결과를 입력을 수신하므로 앞으로 가서 다른 작업을 수행할 수 있다. 각 .then() 블록은 다른 프로미스를 반환한다. 즉, 여러 .then() 블록을 서로 연결할 숭 있으므로 여러 비동기 작업을 순서대로 실행할 수 있다.
- catch() : .then() 실패하면 실행되는 블록으로 동기식 try ... catch 블록과 유사한 방식이다.
Promise VS Callback
Promise는 이전 스타일의 콜백과 유사한 부분이 있으며, 본질적으로 콜백을 함수에 전달하지 않고 콜백 함수를 반환하는 객체이다.
promise는 비동기 작업을 처리하기 위해 만들어졌으며, 이전 스타일의 콜백에 비해 많은 이점이 있다.
- .then()하나의 결과를 다음 작업에 입력으로 전달할 수 있다. (콜백은 종종 지저분한 콜백 지옥을 유발함..)
- Promise 콜백은 항상 이벤트 대기열에 배치된 엄격한 순서로 호출된다.
- 오류 처리가 훨씬 더 좋다. 모든 오류는 .catch()의 각 수준에서 개별적으로 처리되지 않고 블록 끝의 단일 블록에서 처리된다.
- Promise는 콜백을 타사 라이브러리에 전달할 때 함수가 실행되는 방식을 완전히 제어하지; 못하는 콜백과 달리 제어 역전을 방지한다.
Event Queue
Promise와 같은 비동기 작업은 메인 스레드가 처리를 완료한 후 실행되는 이벤트 큐에 넣어 후속 JavaScript 코드가 실행되는 것을 차단하지 않는다. 대기 중인 작업은 가능한 빨리 완료된 다음 JavaScript 환경에 결과를 반환한다.
async/await의 기본
ES7에서 추가된 키워드로, 기본적으로 비동기 코드를 쓰고 읽는 것을 더 쉽게 만들며 promise에 구문적 슈가 역할을 한다.
async 키워드
async 카워드는 함수 선언 앞에 넣어 함수를 비동기로 전환한다. 비동기 함수는 await 비동기 코드를 호출하는데 키워드가 사용될 가능성을 예상하는 방법을 알고 있는 함수이다.
let hello = async () => "Hello";
hello().then((value) => console.log(value))
promise의 반환 결과를 실제로 사용하려면, .then() 블록을 사용한다.
await 키워드
async 함수의 장점은 await 키워드와 함께 사용할 때 분명해진다.
await는 async 함수 내부에서 사용되며, await 프로미스가 이행될 때까지 해당 라인에서 코드를 일시 중지한다.
fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.blob();
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
위의 예시는 프로미스를 이용한 비동기 코드이다.
해당 예시를 async/await 구문으로 변경하면 아래와 같다.
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
프로미스에서는 .then()을 이용해 순서를 제어할 수 있지만, 연결된 처리가 많아지면 .then() 구문이 반복되어 가독성이 떨어진다.
async/await 구문을 사용하면, 비동기 함수가 처리가 완료될 때까지 기다렸다가 결과를 받아오기 때문에 순서가 유지되면서 가독성까지 갖출 수 있다.
async/await 브라우저 지원
async/await를 사용하고 싶지만 이전 브라우저 지원이 우려되는 경우 Babel 라이브러리 사용을 고려해볼 수 있다.
Babel 라이브러리를 통해 최신 JavaScript를 사용해 애플리케이션 작성이 가능하며, 사용자 브라우저에 필요한 변경 사항이 있는 경우 Bable이 파악할 수 있다. async/await를 지원하지 않는 브라우저를 만다면 Babel의 폴리필이 자동으로 이전 브라우저에서 작동하는 폴백을 제공할 수 있다.
Reference