비동기 자바스크립트

Dec 06, 2024 · 8일 전

비동기 자바스크립트

ES6에서 도입한 프라미스는 비도이 동작의 아직 사용할 수 없는 결과를 나타내는 객체입니다. 키워드 async와 await은 ES2017에서 도입했는데, 프라미스 기반 코드를 마치 동기적인 코드처럼 작성할 수 있게 해서 비동기 프로그래밍을 단순화하는 새 문법을 제공합니다. 마지막으로, ES2018에서는 비동기 이터레이터와 for/await 루프를 도입해서 동기적인 것처럼 보이는 단순한 루프에서 비동기 이벤트 스트림을 다룰 수 있게 합니다.

자바스크립트에서 비동기 코드를 다루는 강력한 기능을 제공하면서도 정작 그 코어에는 비동기적인 부분이 전혀 없다는 점은 아이러니 합니다. 따라서 프라미스, async, await, fot/await 를 이해하기 위해서는 먼저 클라이언트 사이드, 서버 사이드 자바스크립트를 보면서 웹 브라우저와 노드의 비동기적 기능에 대해 이해해야 합니다.

콜백과 비동기 프로그래밍

자바스크립트에서 가장 기본적인 비동기 프로그래밍은 콜백을 통해 이뤄집니다. 콜백은 다른 함수에 전달하는 함수입니다. 콜백을 전달받은 함수는 어떤 조건을 만족하거나 어떤 이벤트가 일어나면 여러분이 제공한 함수를 호출합니다. 전달한 콜백 함수를 호출할 때는 조건이나 이벤트에 대한 정보를 제공하며 때때로 함수 인자를 통해 세부 사항을 추가로 제공하기도 합니다. 콜백 시스템은 예제를 보면 쉽게 이해할 수 있습니다. 이어지는 하위 절에서 클라이언트 사이드 자바스크립트와 노드에서 사용하는 콜백 기반 비동기 프로그래밍의 다양한 유형을 설명합니다.

타이머

일정 시간이 지나면 코드를 실행하는 것도 단순한 비동기 프로그램 유형 중 하나입니다.

setTimeout(checkForUpdates, 60000);

setTimeout()의 첫 번째 인자는 함수이고 두 번째 인자는 밀리초로 지정한 시간입니다. 위 코드는 setTimeout()을 호출하고 60,000밀리초, 즉 1분이 지나면 checkForUpdates() 함수를 호출합니다. checkForUpdates()는 여러분이 프로그램에서 정의한 콜백 함수이며 setTimeout()은 콜백 함수를 등록하고 호출할 비동기 조건을 지정하기 위해 호출하는 함수입니다. setTimeout()은 인자는 전달하지 않고 지정된 콜백 함수를 한 번 호출하고서, 그 함수에 대해 잊어버립니다. 한 번 호출하고 마는 함수 말고 정말로 업데이트를 체크하는 함수가 필요하다면 반복적으로 실행해야 합니다. 이런 경우에는 setTimeout()대신 setInterval()을 사용합니다.

// checkForUpdates를 1분 뒤에 호출하고 1분마다 다시 호출합니다.
let updateIntervalId = setInterval(checkForUpdates, 600000);
// setInterval()이 반환하는 값을 clearInterval()에 넘겨 반복 호출을 중단할 수 있습니다.
// 마찬가지로 setTimeout()은 clearTimeout()에 전달할 수 있는 값을 반환합니다.
function stopCheckingForUpdates() {
clearInterval(updateIntervalId);
}

이벤트

클라이언트 사이드 자바스크립트 프로그램은 거의 대부분 이벤트 주도적입니다. 이들은 미리 지정된 계산을 실행하기 보다는 사용자가 뭔가 하길 기다렸다가 그 행동에 반응합니다. 웹 브라우저는 사용자가 키를 누르고, 마우스를 움직이고, 마우스 버튼을 클릭하고, 터치스크린을 터치할 때 이벤트를 일으킵니다. 이벤트 주도 자바스크립트 프로그램은 지정된 컨텍스트에 지정된 타입의 이벤트를 처리할 콜백 함수를 등록하고, 웹 브라우저는 지정된 이벤트가 일어날 때마다 함수를 호출합니다. 이런 콜백 함수를 이벤트 핸들러, 이벤트 리스너라고 부르며 addEventListener()를 통해 등록합니다.

// CSS 선택자에 일치하는 HTML <button> 요소를 나타내는 객체를 웹 브라우저에 요청합니다.
let okay = document.querySelector("#confirmUpdateDialog button.okay");
// 사용자가 버튼을 클릭하면 호출될 콜백 함수를 등록합니다.
okay.addEventListener("click", applyUpdate);

이 예제의 applyUpdate()는 어딘가에 만들었다고 가정한 가상의 콜백 함수입니다. document.querySelector()를 호출하면 웹 페이지에서 지정된 요소를 하나 찾아 그를 참조하는 객체를 반환합니다. 이 요소에서 addEventListener()를 호출해 콜백을 등록합니다. addEventListener()의 첫 번째 인자는 주시할 이벤트를 지정하는 문자열입니다. 사용자가 해당 요소를 클릭하면 브라우저는 applyUpdate() 콜백 함수를 호출하고 클릭 시간이나 마우스 좌표 같은 세부 사항이 포함된 객체를 전달합니다.

네트워크 이벤트

네트워크 요청 역시 자바스크립트 프로그래밍의 대표적인 비동기 유형 중 하나입니다. 브라우저에서 실행되는 자바스크립트는 다음과 같은 코드로 웹 서버에서 데이터를 가져올 수 있습니다.

function getCurrentVersionNumber(versionCallback) {
// 백엔드의 버전 API에 HTTP요청을 보냅니다.
let request = new XMLHttpRequest();
request.open("GET", "http://www.example.com/api/version");
request.send();
// 응답을 받았을 때 호출할 콜백을 등록합니다.
request.onload = function () {
if (request.status === 200) {
// HTTP 상태가 OK면 버전 번호를 가져와서 콜백을 호출합니다.
let currentVersion = parseFloat(request.responseText);
versionCallback(null, currentVersion);
} else {
// 그렇지 않다면 콜백에 에러를 보고합니다.
versionCallback(response.statusText, null);
}
};
// 네트워크 에러가 생겼을 때 호출할 다른 콜백을 등록합니다.
request.onerror = request.ontimeout = function (e) {
versionCallback(e.type, null);
};
}

클라이언트 사이드 자바스크립트 코드는 XMLHttpRequest 클래스와 콜백 함수를 사용해 HTTP 요청을 보내고 서버의 응답을 비동기적으로 처리할 수 있습니다. 여기서 정의한 getCurrentVersionNumber() 함수는 HTTP 요청을 보내고, 서버의 응답을 받거나 타임아웃 또는 기타 에러로 요청이 실패했을 때 호출할 이벤트 핸들러를 등록합니다.

위 코드는 이전 예제처럼 addEventListener()를 호출하지 않았습니다. 대부분의 웹 API는 이벤트를 발생시키는 객체에서 addEventListener()를 호출하는 방식으로 이벤트 핸들러를 정의하며, 콜백 함수와 함께 주시할 이벤트를 전달합니다. 그러나 더 일반적으로 객체의 프로퍼티에 이벤트 리스너를 직접 할당하는 방식으로도 등록할 수 있습니다. 이 예제에서는 onload, onerror, ontimeout 프로퍼티에 함수를 할당했습니다. 관습적으로 이런 형태의 이벤트 리스너 프로퍼티 이름은 항상 on으로 시작합니다. addEventListener()는 여러가지 이벤트 핸들러를 등록할 수 있는 더 유연한 해결책 입니다. 하지만 지금처럼 같은 객체, 같은 이벤트 타입에 다른 리스너를 추가로 등록하지 않을 것이라고 확신한다면 적절한 콜백 함수를 프로퍼티로 할당하는 편이 더 간편합니다.

이 예제의 getCurrentVersionNumber() 함수에서 한 가지 더 주목할 점은 이 함수가 비동기로 요청을 보내기 때문에 현재 버전 번호를 동기적으로 반환할 수 없다는 것 입니다. 대신 호출자는 결과를 받거나 에러가 일어나면 호출될 콜백 함수를 전달합니다. 여기서 사용한 콜백 함수는 인자 두 개를 받습니다. XMLHttpRequest가 정확히 동작하면 getCurrentVersionNumber()는 콜백을 호출하면서 첫 번째 인자로 null을, 두 번째 인자로 버전 번호를 전달합니다. 에러가 일어나면 getCurrentVersionNumber()는 콜백을 호출하면서 첫 번째 인자로 에러로 관한 세부 사항을, 두 번째 인자로는 null을 전달합니다.

노드의 콜백과 이벤트

서버 사이드 자바스크립트 환경인 노드는 비동기적으로 만들어져 있으며 많은 API가 콜백과 이벤트를 사용합니다. 예를 들어 파일 콘텐츠를 읽는 기본 API도 비동기적이며 파일 콘텐츠를 읽으면 콜백 함수를 호출합니다.

const fs = require("fs");
let options = {
// 여기에 기본 옵션을 작성합니다.
};
// 설정 파일을 읽고 콜백 함수를 호출합니다.
fs.readFile("config.json", "utf-8", (err, text) => {
if (err) {
// 에러가 있으면 경고를 표시하고 계속 진행합니다.
console.warn("Could not read config file:", err);
} else {
// 에러가 없으면 파일 콘텐츠를 분석하고 옵션 객체에 할당합니다.
Object.assign(options, JSON.parse(text));
}
// 어느 쪽이든 이제 프로그램을 실행할 수 있습니다.
startProgram(options);
});

노드의 fs.readFile() 함수는 매개변수를 두 개 받는 콜백을 마지막 인자로 받습니다. fs.readFile()은 지정된 파일을 비동기적으로 읽고 콜백을 호출합니다. 파일을 성공적으로 읽었다면 파일 콘텐츠를 두 번째 콜백 인자로 전달합니다. 에러가 있었다면 에러를 첫 번째 콜백 인자로 전달합니다. 이 예제에서는 콜백에 화살표 함수를 사용했는데, 이런 단순한 동작에는 화살표 함수의 간결하고 자연스런 문법이 잘 어울립니다. 노드에는 이벤트 기반 API도 다양합니다. 다음 함수는 노드에서 URL에 HTTP요청을 보내는 방법입니다. 이 함수에는 이벤트 리스너로 처리하는 비동기 코드 계층이 두 개 있습니다. 노드는 addEventListener() 대신 on() 메서드를 사용해 이벤트 리스너를 등록합니다.

const https = require("https");
// URL의 텍스트 콘텐츠를 읽고 비동기적으로 콜백에 전달합니다.
function getText(url, callback) {
// URL에 HTTP GET 요청을 시작합니다.
request = https.get(url);
// 응답 이벤트를 처리할 함수를 등록합니다.
request.on("response", (response) => {
// 응답 이벤트가 있다는 것은 응답 헤더를 받았다는 의미입니다.
let httpStatus = response.statusCode;
// HTTP 응답의 바디는 아직 받지 못했으므로
// 바디를 받았을 때 호출할 이벤트 핸들러를 등록합니다.
response.setEncoding("utf-8");
let body = "";
// 바디의 텍스트 덩어리를 사용할 수 있게 되면 이 이벤트 핸들러를 호출합니다.
response.on("data", (chunk) => {
body += chunk;
});
// 응답이 완료되면 이 이벤트 핸들러를 호출합니다.
response.on("end", () => {
if (httpStatus === 200) {
callback(null, body);
} else {
callback(httpStatus, null);
}
});
});
// 저수준 네트워크 에러를 처리할 이벤트 핸들러도 등록합니다.
request.on("error", (err) => {
callback(err, null);
});
}

참조

  • the Definitive Guide (서적)