기본적으로 코드들은 한 작업을 끝내고 다른 작업을 시작한다. 이 상황을 동기적(synchronous) 이라고 한다. 다른 말로 표현하면 한 작업이 끝날 때까지는 다른 작업은 실행하지 않는다는 뜻이다. 한 작업이 실행될 때 다른 작업을 막는 것을 블로킹(blocking)이라고 한다. 이것은 어쩌면 비효율적일 수 있다.
비동기는 한 작업이 끝나길 다른 작업이 기다리지 않고 다음 코드를 수행하는 것을 의미한다.
브라우저에서 제공하는 Web API이며 비동기로 작동한다.
일정 시간 후에 함수를 실행한다.
setTimeout(function() {
console.log('1초 후 실행');
}, 1000)
첫번째 파라미터로 함수를 받고, 두번째 인자로 밀리초를 받는다. setTimeout 함수가 실행되면 밀리초 이후에 첫번째 파라미터 함수를 실행한다.
이 함수의 리턴값은 임의의 타이머 ID다.
setTimeout 타이머를 종료한다.
const timer = setTimeout(function() {
console.log('1초 후 실행');
}, 1000)
clearTimeout(timer);
리턴 값이 타이머 ID이기 때문에, 매개변수로 타이머 ID를 전달하면 그 함수를 종료한다.
일정한 시간을 갖고 함수를 반복적으로 실행한다.
setInterval(function() {
console.log('1초마다 실행');
}, 1000)
첫번째 파라미터는 콜백 함수(반복적으로 실행할 함수), 두번째 파라미터는 반복할 시간(밀리초)를 전달한다.
setInterval 타이머를 종료한다.
const timer = setInterval(function() {
console.log('1초마다 실행');
}, 1000);
cleartInterval(timer);
setInterval 리턴값이 타이머 ID이기 때문에, clearInterval의 전달인자로 타이머 ID를 보내주면 타이머가 종료된다.
비동기 코드는 코드가 작성된 순서대로 작동되는 것이 아니라 동작이 완료되는 순서대로 작동한다. 그래서 비동기 코드들의 순서를 예측할 수 없다.
비동기로 작동하는 코드를 제어하는 방법으로 Callback을 사용한다. Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있다.
const printString = (string, callback) => {
setTimeout(function() {
console.log(string);
callback();
}, Math.floor(Math.random() * 100) + 1);
}
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {});
});
});
}
printAll();
printString 함수는 각 함수마다 다른 딜레이 시간을 가지고 있다. 하지만 콜백을 이용해서 다른 시간이어도 순서를 정해서 실행할 수 있다.
하지만 이렇게 계속 작업하다 보면 가독성이 심하게 떨어지고 depth가 깊어진다. Callback Hell에 빠질 수가 있다.
Promise라는 class를 이용해 비동기로 작동하는 코드를 제어할 수 있다. 또한 콜백 지옥도 빠져나올 수가 있다. 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받는데 이 콜백 함수는 resolve, reject 함수를 인수로 전달받는다.
new Promise가 반환하는 Promise 객체는 state, result 내부 프로퍼티를 갖는다. 하지만 직접 접근할 수 없고, .then, .catch, .finally의 메서드를 사용해야 접근이 가능하다.
class Promise {
constructor(resolve, reject) {
this.resolve = function() {}
this.reject = function() {};
this.state = pending;
this.result;
}
then(reslove) {
...
return this;
};
catch(reject) {
...
return this;
};
finally() {};
}
어떻게 Promise class가 어떻게 생긴진 모르겠지만 아마 이렇게 생겼을 것이다.
기본 상태는 pending(대기). 비동기 처리를 수행할 콜백 함수(executor)가 성공적으로 작동했다면, 상태는 fulfilled(이행)로 변경되고, 에러가 발생했다면 rejected(거부) 상태가 된다.
기본 상태는 undefined. 콜백 함수(executor)가 성공적으로 작동하면resolve(value)가 호출되며 value로, 에러가 발생하여 reject(error)가 호출되면 error로 변한다.
executor에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve 함수를 호출하고, .then 메서드로 접근할 수 있다. 또한 .then 안에서 리턴한 값이Promise면 Promise의 내부 프로퍼티 result를 다음 .then 의 콜백 함수의 인자로 받아로고, Promise가 아니라면 리턴한 값을 .then 의 콜백 함수의 인자로 받아올 수 있다.
let promise = new Promise(() => { // 변수 promise에 Promise 객체 할당.
resolve("성공")
});
promise.then(value => { // Promise 객체는 .then을 쓸 수 있다.
console.log(value); // "성공"
})
executor에 작성한 코드들이 에러가 발생했을 때 reject 함수를 호출하고 .catch 메서드로 접근할 수 있다.
let promise = new Promise(function(resolve, reject) {
reject(new Error("에러"));
})
promise.catch(error => {
console.log(error);
})
executor에 작성했던 코드들이 정상 처리 여부와 상관없이 .finally 메서드로 접근할 수 있다.
let promise = new Promise(function(resolve, reject) {
resolve("성공");
})
promise
.then(value => {
console.log(value);
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log("마지막에 실행되며 성공, 실패 뭐가 오든 작동");
})
JavaScript ES8에서 async/await 키워드를 제공한다. async 함수 내에서만 await를 사용하고 await 키워드 뒤에 오는 Promise 객체가 동작하고 나서야 다음 순서의 코드가 동작한다. 즉, 비동기 코드를 동기적으로 순서를 간단하게 적을 수 있다.
const asyncFunc = async function() {
await 작성하고자 하는 코드 1 // 비동기를 동기적으로
await 작성하고자 하는 코드 2
}
비동기에 대해서도 조금 어려운데 Promise 객체를 이해하는 데 있어 좀 어렵고, 복잡하다. 이것 또한 반복으로 익숙해지면 되겠지...