비동기 프로그래밍
비동기 프로그래밍
어떤 일이 완료되기를 기다리지 않고 다음 코드를 실행해 나가는 방식을 뜻함
비동기 프로그래밍 방식은 대개 프로그램의 성능과 응답성을 높이는 데에 도움
코드가 실제로 실행되는 순서가 뒤죽박죽이 되므로, 코드의 가독성을 해치고 디버깅을 어려운 단점
타이머 API (Motivation)
setTimeout & setInterval
각각 타이머 식별자 반환. 실행 중인 타이머를 취소할 수 있으며, 정확한 지연시간을 보장해 주지 않음
setTimeout(() => {
console.log('실행된 지 2초 지남')
});
setInterval(() => {
console.log('3초마다 출력');
}, 3000);
JavaScript 코드 실행 과정
호출 스택(Call Stack)
스택 형태의 저장소. 함수 호출과 관련된 정보를 관리
복잡한 코드의 동작을 단순한 자료구조로 표현할 수 있음
웹 브라우저는 호출 스택에 실행 맥락이 존재하는 동안, 즉 실행 중인 함수가 존재하는 동안에는 먹통이 될 수 있어 사용자와의 상호작용을 위한 코드 작석 시 실행 시간 염두
- 스크립트를 불러올 때, 전역 실행 맥락(global execution context)을 호출 스택에 추가
- 함수가 호출되면, 해당 호출에 대한 실행 맥락을 생성해서 호출 스택에 추가(push)
- 변수에 대입이 일어나면, 호출 스택에 저장되어 있는 변수의 내용을 변경
- 함수의 실행이 끝나면, 결과값 반환 및 호출 스택 가장 상단에 있는 실행 맥락을 제거(pop)
- 스크립트의 실행이 모두 끝나면, 전역 실행 맥락을 호출 스택에서 제거(pop)
실행 맥락(execution context)
호출 스택에 저장되는 각 항목을 실행 맥락이라 부름
- 함수 내부에서 사용되는 변수
- 스코프 체인
- this가 가리키는 객체
작업 큐 (Task Queue)
브라우저에서는 다음과 같은 절차를 통해 오래 기다려야 하는 일을 처리 가능
- JavaScript 엔진에서 직접 처리하는 것이 아니라 API를 통해 브라우저에 위임. 작업이 끝나면 실행시킬 콜백 등록
- 작업이 종료되면 그 결과와 콜백을 작업 큐에 추가
- 브라우저는 호출 스택이 비워질 때마다 작업 큐에서 가장 오래된 작업을 꺼내와서 해당 작업에 대한 콜백 실행. 이를 이벤트 루프(event loop)
작업큐의 성질
- 작업 큐에 쌓인 순서대로 실행
- 뒤늦게 추가된 작업은 앞서 추가된 작업이 모두 실행된 후 실행
- 호출 스택이 비워지지 않는다면, 작업 큐에 쌓여있는 작업을 처리할 수 없음
- 각 작업 사이에 브라우저는 화면을 새로 그릴 수 있음
콜백(Callback)
다른 함수의 인수로 넘기는 함수를 말함. 데이터가 준비된 시점에서만 원하는 동작을 수행
function getData(callbackFunc) {
$.get('https://domain.com/products/1', function(response) {
callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
});
}
getData(function(tableData) {
console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});
콜백 지옥(Callback hell)
비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제
$.get('url', function(response) {
parseValue(response, function(id) {
auth(id, function(result) {
display(result, function(text) {
console.log(text);
});
});
});
});
콜백 지옥 해결 방법
Promise나 Async를 사용하는 방법.
코딩 패턴으로만 콜백 지옥을 해결하려면 아래와 같이 각 콜백 함수를 분리
function parseValueDone(id) {
auth(id, authDone);
}
function authDone(result) {
display(result, displayDone);
}
function displayDone(text) {
console.log(text);
}
$.get('url', function(response) {
parseValue(response, parseValueDone);
});
Promise
콜백 문제 해결하기 위해 Promise 패턴을 사용한 여러 라이브러리 등장하고,
결국 이 라이브러리들이 표준화되어, 결국 ES2015에 이르러 JavaScript 언어 자체에 포함
언젠가 끝나는 작업의 결과값을 담는 통과 같은 객체
then 메소드를 통해 콜백을 등록해서, 작업이 끝났을 때 결과값을 가지고 추가 작업
- then 메소드는 Promise 객체를 반환하므로, 콜백을 중첩하지 않고도 비동기 작업을 연이어 할 수 있음
- 비동기 작업이라는 동작 자체를 값으로 다룰 수 있게 됨
Promise.resolve 메소드
콜백으로 인수를 받으며, 콜백 안에서 resolve를 호출하면 resolve에 인수로 준 값이 곧 Promise 객체의 궁극적인 결과값이 됨
// 비동기 작업을 하는 Promise 생성자
const p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('2초가 지났습니다.');
resolve('hello');
}, 2000);
});
then 메소드
Promise 객체의 결과값을 사용해 추가 작업하는 메소드
Promise 객체를 반환. 반환한 값이 곧 Promise 결과값
p.then(msg => {
console.log(msg); // hello
});
const p2 = p.then(msg => {
return msg + ' world';
});
p2.then(msg => {
console.log(msg); // hello world
});
// 상단 코드 줄이기
p.then(msg => {
return msg + ' world';
}).then(msg => {
console.log(msg);
});
HTTP 통신 할 때 Promise 사용
const axios = require('axios');
const API_URL = 'https://api.github.com';
axios.get(`${API_URL}/repos/facebookincubator/create-react-app/issues?per_page=10`)
.then(res => {
console.log('최근 10개의 이슈:');
res.data
.map(issue => issue.title)
.forEach(title => console.log(title));
console.log('출력이 끝났습니다.');
});
비동기 함수 (Async Function)
ES2017에서 도입된 비동기 함수(async function)를 사용하면, 동기식 코드와 거의 같은 구조를 갖는 비동기식 코드를 짤 수 있음
함수 앞에 async 키워드를 붙이면, 이 함수는 비동기 함수가 됨
비동기 함수는 항상 Promise 객체를 반환한다는 특징
// 비동기 함수
async function func1() {
// ...
}
// 비동기 화살표 함수
const func2 = async () => {
// ...
}
// 비동기 메소드
class MyClass {
async myMethod() {
// ...
}
}
await
비동기 함수 내에서 await 키워드 뒤에 오는 Promise가 결과값을 가질 때까지 비동기 함수의 실행을 중단
await는 연산자이기도 하며, await 연산의 결과값은 뒤에 오는 Promise 객체의 결과값
await 키워드는 for, if와 같은 제어 구문 안에서도 쓰일 수 있기 때문에,
then 메소드를 사용할 때보다 복잡한 비동기 데이터 흐름을 아주 쉽게 표현할 수 있다는 장점
async function main() {
await delay(1000);
await delay(2000);
const result = await Promise.resolve('끝');
console.log(result);
}
main();