JavaScript에서 볼 수 있는 비동기 스타일은 두 가지 유형이 있다.
예전 방식인 콜백(Callback), 새로운 방식인 약속(Promise)
비동기 콜백(Async callbacks)
- 콜백은 JavaScript 함수를 사용하는 규칙의 이름일뿐이다.
-> 백그라운드에서 코드 실행을 시작할 함수를 호출할 때 인수로 지정된 함수를 의미한다.
- 콜백 함수를 다른 함수의 인수로 전달할 때, 함수의 참조를 인수로 전달하는 것이며 즉시 실행되지 않는다. 실질적인 콜백 함수의 실행은 호출한 함수의 몸체에서 해당 콜백 함수가 실행되어 전달한 함수의 코드가 수행된다.
- 모든 콜백이 비동기적으로 동작하지는 않는다.
예를 들어 forEach 함수는 단순히 배열 요소별로 콜백함수를 실행시키기만 한다. 자신이 실행한 콜백함수가 비동기적으로 동작하는지는 신경쓰지 않는다. (배열 요소별로 실행한 콜백함수가 끝나는 것을 기다리지 않는다.)
예: Array.prototype.forEach
- 콜백은 함수가 실행되는 순서, 함수간에 어떤 데이터가 전달되는지를 제어할 수 있으며 또한 상황에 따라 다른 함수로 데이터를 전달할 수 있다.
function loadAsset(url, type, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = type;
//XHR 요청이 진행되는 동안 대기
xhr.onload = () => {
callback(xhr.response);
}
xhr.send();
}
function displayImage(blob) {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
//콜백함수: displayImage
loadAsset('./coffee.jpg', 'blob', displayImage);
콜백 함수를 계속 중첩해서 사용하다보면 콜백 지옥이라 불리는 문제를 만나 볼 수 있는데 이는 코드 가독성을 해치며 유지보수를 어렵게 한다. 약속(Promise)은 이러한 기존 콜백 함수의 문제점을 개선하기 위해 나온 새로운 코드 스타일이라 볼 수 있다.
약속(Promise)
- 어떤 작업의 중간상태를 나타내는 객체.
- 약속(Promise)는 비동기 작업이 성공 했는지 혹은 실패했는지를 나타내는 하나의 객체.
- 미래에 어떤 종류의 결과가 반환됨을 약속(Promise) 해주는 객체.
Promise 상태
- 약속(Promise)이 생성되면 그 상태는 성공도 실패도 아닌 pending 상태라고 부른다.
- 약속(Promise) 결과가 반환되면 결과에 상관 없이 resolved 상태라고 부른다.
- 성공적으로 처리된 약속(Promise)는 fulfilled 상태이다. 이 상태가 되면 Promise 체인의 다음 .then() 블럭에서 사용할 수 있는 값을 반환하며 .then() 블럭 내부의 executor 함수에 약속(Promise)에서 반환된 값이 파라미터로 전달된다.
- 실패한 약속(Promise)는 rejected 상태이다. 이때 어떤 이유(reason) 때문에 약속(Promise)이 rejected 됐는지를 나타내는 에러 메시지를 포함한 결과가 반환된다. Promise 체이닝의 제일 마지막 .catch() 에서 상세한 에러 메시지를 확인할 수 있다.
console.log ('Starting');
let image;
fetch('./coffee.jpg').then((response) => {
console.log('It worked :)')
return response.blob();
}).then((myBlob) => {
let objectURL = URL.createObjectURL(myBlob);
image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch((error) => {
console.log('There has been a problem with your fetch operation: ' + error.message);
});
console.log ('All done! '+ image.src); //에러 발생
- fetch(): URL 호출 성공여부 상태를 약속(Promise) 객체로 반환
- then(): 이전 작업이 성공했을때 수행할 작업을 나타내는 콜백(Callback) 함수. 그리고 각 콜백(Callback)함수는 인수로 이전 작업의 성공 결과를 전달받는다. 각 then()은 서로 다른 약속(Promise)을 반환하기 떄문에 then()을 여러개 사용하여 연쇄적으로 비동기 작업을 차례대로 수행할 수 있다.
- catch(): .then() 이 하나라도 실패하면 동작한다. 이는 동기 방식의 try...catch 블럭과 유사하다.
error 오브젝트는 catch() 블럭 안에서 사용할 수 있으며, 발생한 오류를 보고하는 용도로 사용할 수 있다.
그러나 try...catch는 async/await에서는 동작하지만, 약속(Promise)와 함께 동작할 수 없다.
이벤트 큐
- 약속(Promise)와 같은 비동기 작업은 이벤트 큐(Event Queue)에 들어간다.
- 메인 스레드가 끝난 후 실행되어 후속 JavaScript 코드가 차단되는것을 방지한다.
콜백(Callback) vs 약속(Promise)
약속(Promise)의 이점
- 약속(Promise)은 여러 개의 연쇄 비동기 작업을 할 때 앞서 본 예제처럼 .then() 을 사용하여 결과값을 다음 작업으로 넘길 수 있다. 콜백(Callback)으로 이를 처리하려면 훨씬 더 어렵고 콜백 지옥으로 끝날 수 있다.
- Promise 콜백은 항상 이벤트 큐에 배치되는 엄격한 순서로 호출된다.
-> 마이크로 태스크큐(microtasks)에 저장된다.
- 에러 처리가 더 간결해진다. 모든 에러는 콜백 함수의 각 수준에서 개별적으로 처리되는 것이 아니라 블록 끝에 있는 단 한개의 .catch() 블록으로 처리할 수 있다.
- 구식 콜백(Callback)은 써드파티 라이브러리에 전달될 때 함수가 어떻게 실행되어야 하는 방법을 상실하지만 Promise는 그렇지 않다.
이유는 써드파티 라이브러리에서 실질적으로 전달한 콜백 함수를 실행하기 때문인데 이는 함수의 제어권이 써드파티 라이브러리로 이동했다는 것을 뜻하며 콜백 함수의 실행 시점, 어떤 인자와 이 함수를 호출해야 할 것인가에 대한 제어를 할 수 없다.
약속(Promise)의 경우 설계상 제어권을 써드파티 라이브러리에게 넘기지 않고 오히려 써드파티 라이브러리에서 값을 전달받아 사용하기 때문에 우리가 직접 함수의 실행 시점, 함수를 호출할 때 전달되는 인자를 관리할 수 있다. 즉, 약속(Promise)에서는 제어의 역전이 발생하지 않는다.
비동기 코드의 특성
- 비동기 코드 블럭에서 반환된 결과를 동기화 코드 블럭에 사용할 수 없다.
브라우저가 동기화 코드 블럭을 처리하기 전에 비동기 코드 블럭 작업이 완료됨을 보장할 수 없기때문에 위의 예제 마지막 부분에 있는 image.src를 호출하게 되면 에러가 발생한다..
[참고자료]
MDN WEB DOCS - Graceful asynchronous programming with Promises
MDN WEB DOCS - Introducing asynchronous JavaScript
MDN WEB DOCS - NodeList.prototype.forEach()
자바스크립트에서 비동기 코드를 처리 하는 방식에 대하여
'JavaScript > 기본' 카테고리의 다른 글
자바스크립트 비동기(Javascript Asynchronous) 처리 이해 - async, await, 약속(Promise) - 2 (0) | 2021.05.05 |
---|---|
자바스크립트(Javascript) - 동기(Synchronous), 비동기(Asynchronous) 방식 이해 (0) | 2021.05.03 |
자바스크립트(Javascript) - 객체(Object)와 프로토타입(Prototype) (0) | 2021.04.27 |