비동기와 동기
- Synchronous는 동기라고 하고 순차적으로 흐르는 케이스임, Asynchronous의 경우 비동기로 순차적인게 아닌, 실행 순서와 실행 Flow가 동시에 즉 병렬적으로 흐르는 케이스임
- JS는 비동기 처리를 하는 경우가 많음
- 동기는 결국, 코드가 그대로 쭉 실행되는 것을 동기라고 해석하고 볼 수 있음(어느정도 순서가 있게, 위에서 아래로 실행된다고 생각하는 등)
- 하지만 그러지 않은 경우가 많은데 그럴 때 비동기의 개념이 들어감, 이 때 쓰레드를 알아야함
- 쓰레드는 프로그램의 실행 단위라고도 함, JS는 싱글 스레드 언어임, 그래서 비동기로 실행되는 경우가 생기는 것임
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
alert('You clikced me!');
let pElem = document.createElement('p');
pElem.textContent =
'This is a newly-added paragraph.';
document.body.appendChild(pElem);
});
- 위의 케이스의 경우도 클릭시 alert이 뜨고 그 다음에 확인을 눌러야지 p element를 만들고, text content를 만든 뒤, document.body에 추가를 함
- 이는 아무리 비동기 실행이더라도 싱글 스레드 언어이기 때문에 한 번에 한 작업만 하기 때문임, 한 작업만 하나의 main thread에서 작업을 할 수 있음
- 아래의 경우에도, 코드가 순서대로 출력이 되는게 아닌 시작, 종료, SetTimeout으로 나옴
console.log('시작 ===');
setTimeout(() => {
console.log('Set Timeout');
}, 1000);
console.log('종료 ===');
- 이처럼 싱글 스레드 언어이기에 그런식의 출력이 됨
Callback
- 앞서 쓴, setTimeout은 비동기로 실행되고 싱글 쓰레드, 그리고 이벤트 루프로 처리되서 순서대로 실행이 되지 않는 것임
- callback 패턴은 함수의 사용권을 위임하는 것임, 아래 예시처럼 filter를 흉내내면 callback으로 아래와 같이 실행되고 처리된다고 이해할 수 있음
const isFilter = function (ele, index, array) {
console.log(ele);
};
[(1, 2, 3)].filter(isFilter);
Array.prototype.filter = function (callback) {
const array = this;
for (let i = 0; i < array.length; i++) {
callback(array[i], i, array);
}
};
Element.addEventListener('click', (e) => console.log(e));
Element.addEventListener = function (eventType, callback) {
if (eventType === 'click') {
const clickEventObject = {
target: {},
};
callback(clickEventObject);
}
};
- 그래서 callback이 함수의 사용권을 위임한다고 하는 것임, 이에 대해서 setTimeOut을 callback을 활용해서 실행해볼 수 있음
console.log('1');
function setTimeoutWithCallback(callbackFunc) {
setTimeout(() => {
console.log('2');
callbackFunc()
}, 1000);
}
setTimeoutWithCallback(() => console.log('3'))
- 위의 로직의 경우 callback을 활용해서 순서대로 그 값이 나올 수 있게됨, callback에 console.log('3')을 넘겨서 위임하기에 순서대로 실행된 것
- 마치 아래와 같은 로직으로 실행이 된 것일 것임
setTimeout(function() {
console.log('2')
setTimeout(function () {
console.log('3')
}, 1000)
}, 1000)
- 넘긴 함수만큼 알아서 순차적으로 실행된 것임, 순차적으로 비동기를 실행시킨 것임. 하지만 너무 과하게 쓰면 callback 지옥에 빠질 수 있음
- 알아서 위임된 형태로 콜백 패턴으로 넘겨서 실행한다는 것임, 그렇게 비동기 처리를 하는 것
promise란? & promise resolve & promise reject
- 객체이면서 비동기 작업을 도와주고, 실행 종료 후 완료 & 실패와 그 결과값을 알려줌
- pending(대기) 상태, fulfilled(이행) 상태, rejected(거부) 상태로 초기 상태와 성공 & 실패 상태가 나뉘어지고, 이에 대해서 then 메서드로 이어짐
- 점 연산자로 then 체이닝으로 promise를 사용함, catch의 경우 실패했을 때 처리를 함
- 성공은 then 체이닝으로 실패는 catch 체이닝으로 이어짐
- 이 과정이 계속 반복되고 비동기 연산을 할 수 있음
const promiseResolve = Promise.resolve('성공')
const promiseReject = Promise.reject('실패')
promiseResolve
.then((resolve) => {
console.log('1회' + resolve);
return promiseResolve;
})
.then((resolve) => {
console.log('2회' + resolve);
return promiseResolve;
})
.then((resolve) => {
console.log('3회' + resolve);
return promiseResolve;
})
.then((resolve) => {
console.log('4회' + resolve);
return promiseResolve;
})
- reject의 경우도 마찬가지임(catch 사용 후 안정적으로 resolve도 같이 사용가능)
promiseResolve
.then((resolve) => {
console.log('1회' + resolve);
return promiseResolve;
})
.then((resolve) => {
console.log('2회' + resolve);
return promiseReject;
})
.then((resolve) => {
console.log('3회' + resolve);
return promiseResolve;
})
.then((resolve) => {
console.log('4회' + resolve);
return promiseResolve;
})
promiseResolve
.then((resolve) => {
console.log('1회' + resolve);
return promiseResolve;
})
.then((resolve) => {
console.log('2회' + resolve);
return promiseReject;
})
.catch((reject) => {
console.log('3회 실패' + reject);
return promiseResolve;
})
.then((resolve) => {
console.log('4회' + resolve);
return promiseResolve;
})
new promise
- 앞서 setTimeout을 써서 한 것에 대해서 아래와 같이 Promise를 사용해서 할 수 있음, 이 때 new 키워드 활용
const one = Promise.resolve('1');
const two = (delay) =>
new Promise((resolve) =>
setTimeout(() => {
resolve('2');
}, delay),
);
const three = Promise.resolve('3');
one
.then((oneRes) => {
console.log(oneRes);
return two(3000);
})
.then((twoRes) => {
console.log(twoRes);
return two;
})
.then((threeRes) => {
console.log(threeRes);
}).finally(() => console.log('END'));
- 그러면 위의 경우 1이 나온 뒤 3초 뒤 2, 3, END가 나타남
- 이처럼 비동기를 명시적으로 then 체이닝으로 쓸 수 있음
const starbucks = function(coffeeName) {
return new Promise((resolve, reject) => {
if (coffeeName === '아메리카노') {
resolve('아메리카노 한잔입니다');
} else {
reject('아메리카노는 없습니다.');
}
};
};
starbucks('아메리카노')
.then((res) => console.log(res))
.catch((rej) => console.log(rej))
.finally(() => console.log('감사합니다'));
async & await
- async는 함수에 선언할 수 있는 일종의 키워드임 prefix로 쓰고 그 내부에는 await을 쓸 수 있음
- async - await의 경우, 마치 promise resolve에서 then 체이닝과 비슷하게 쓸 수 있음
- 앞서 만든 starbucks 예시를 async & await으로 바꿔볼 수 있음
const starbucks = function(coffeeName) {
return new Promise((resolve, reject) => {
if (coffeeName === '아메리카노') {
resolve('아메리카노 한잔입니다.');
} else {
reject('아메리카노는 없습니다.');
}
};
};
starbucks('아메리카노')
.then((res) => console.log(res))
.catch((rej) => console.log(rej))
.finally(() => console.log('감사합니다'));
async function americano() {
const result = await starbucks('아메리카노');
return result;
}
americano();
- 이를 동시에 then, catch, finally를 만들기 위해서, 아래와 같이 쓸 수 있음
async function americano(someDrink) {
try {
const result = await starbucks(someDrink);
return result;
} catch (error) {
console.log(error);
} finally {
console.log('감사합니다');
}
}
americano('아메리');
americano('아메리카노');
- async를 사용하면 반드시 await을 붙여줘야함, 그래서 무조건 좋은 것은 아님
- 하지만 장점은 then 체이닝으로 스코프를 유지하는 게 아닌 함수 내부에서 자유롭게 then, catch, finally를 쓸 수 있음
- 상황에 맞게 찾아서 사용하는게 좋음