자바스크립트의 동기 처리란, 특정 코드의 실행이 완료될 때까지 기다리고 난 후 다음 코드를 수행하는 것을 의미한다.
자바스크립트의 비동기 처리는 ‘특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드들을 수행하는 것’을 의미한다.
JavaScript는 싱글 스레드 기반으로 동작하는 언어이기 때문에 동기적으로 작동하게 된다. 그러나 JavaScript에서도 비동기 처리가 가능한 이유는 JavaScript가 작동하는 환경(런타임)에서 비동기 처리를 도와주기 때문에 특별한 작업 없이 비동기 처리를 할 수 있게 된다.
매개변수(parameter)
: 실행할 콜백 함수, 콜백 함수 실행 전 기다려야 할 시간 (밀리초)return 값
: 임의의 타이머 IDsetTimeout(function () {
console.log('1초 후 실행');
}, 1000);
매개변수(parameter)
: 타이머 IDreturn 값
: 없음const timer = setTimeout(function () {
console.log('10초 후 실행');
}, 10000);
clearTimeout(timer);
매개변수(parameter)
: 실행할 콜백 함수, 반복적으로 함수를 실행시키기 위한 시간 간격 (밀리초)return 값
: 임의의 타이머 IDsetInterval(function () {
console.log('1초마다 실행');
}, 1000);
매개변수
: 타이머 IDreturn 값
: 없음const timer = setInterval(function () {
console.log('1초마다 실행');
}, 1000);
clearInterval(timer);
위에서 설명한 함수들을 사용하면 비동기적으로 실행은 되지만, 결과의 순서까지 제어할 수는 없다.
순서를 제어하고 싶다면? callback
, promise
, async/await
사용해야 한다.
const printString = (string) => {
setTimeout {
() => {
console.log(string);
},
}Math.floor(Math.random() * 100) +1
)
}
const printAll = () => {
printString("A")
printString("B")
printString("C"
}
printAll()
코드의 실행 시간을 알 수 없기 때문에 결과는 랜덤하게 A,B,C 가 출력되고 있다.
callback을 사용하면 위에서 제어할 수 없었던 비동기 코드의 순서를 핸들링할 수 있게 된다.
A가 끝나면 callback 실행 B가 끝나면 callback 실행 ...의 반복으로 결과 순서가 ABC로 지켜진다.
const printString = (string, callback) => {
setTimeout {
() => {
console.log(string);
`callback()
},
}Math.floor(Math.random() * 100) +1
)
}
const printAll = () => {
printString("A", () => {
printString("B", () => {
printString("C", () => {})
})
})
}
printAll()
const somethinGonnaHappen = callback => {
waitingUntilSomethinHappens();
if(isSomethingGood) {
callback(null, data)
}
if(isSomethingBad) {
callback(err, null)
}
}
somethingGonnaHappen((err, data) => {
if(err) {
return;
}
return data;
})
콜백이 순차적으로 이루어지면 점점 코드의 가독성이 떨어지고 관리가 어려워지는데 이를 Callback Hell
이라고 한다.
const printAll = () => {
printString("A", () => {
printString("B", () => {
printString("C", () => {})
printString("D", () => {
printString("E", () => {
})
})
})
})
}
프로미스의 특징은 new Promise()로 인스턴스를 생성되는 그 순간 콜백함수의 안의 코드가 수행된다. 때문에 사용자가 요구한 순간에 네트워크 통신과 같은 비동기적 수행이 필요한거라면 아래와 같이 코드를 작성하면 불필요한 네트워크 통신이 일어나게 된다는 점을 유의해야 한다.
const Promise = new Promise ((resolve,reject) => {
console.log('doing something...');
})
프로미스는 다음 중 하나의 상태를 가진다.
new Promise 가 반환하는 Promise 객체는 내부 프로퍼디를 갖지만 직접 접근할 수 없고 .then, .catch, .finally 의 메서드를 사용해야 접근이 가능하다.
executor에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve 함수를 호출하고 .then 메서드로 접근할 수 있다. 또한 .then 안에서 리턴한 값이 Promise면 Promise의 내부 프로퍼티 result를 다음 .then 의 콜백 함수의 인자로 받아오고, Promise가 아니라면 리턴한 값을 .then 의 콜백 함수의 인자로 받아올 수 있다.
let promise = new Promise((resolve, reject) => {
resolve("성공");
});
promise.then(value => {
console.log(value); // 성공
})
executor에 작성했던 코드들이 에러가 발생했을 경우에는 reject 함수를 호출하고 .catch 메서드로 접근할 수 있다.
let promise = new Promise(function(resolve, reject) {
reject(new Error("에러"))
});
promise.catch(error => {
console.log(error); // 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("성공이든 실패든 작동!");
})
비동기 작업을 순차적으로 진행해야 하는 경우에 Promise chaining가 필요하다. Promise chaining이 가능한 이유는 .then, .catch, .finally 의 메서드들은 Promise를 반환하기 때문이다. 따라서 .then을 통해 연결할 수 있고, 에러가 발생할 경우 .catch 로 처리하면 된다.
let promise = new Promise(function(resolve, reject) {
resolve('성공');
...
});
promise
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.catch((error) => {
console.log(error);
return '실패';
})
.finally(() => {
console.log('성공이든 실패든 작동!');
});
Promise.all()은 여러 개의 프로미스를 처리할 때 사용하며, 모든 프로미스가 이행될 때까지 기다렸다 그 결과값을 담은 배열을 반환하는 메서드이다. 그 예로 복수의 URL에 동시에 요청을 보내고, 응답 완료를 기다릴 때 주로 사용한다.
async function pickFruits() {
return Promise.all([getApple(), getBanana()])
.then(fruits => fruits.join("+")
);
}
Promise.race()는 배열의 전달된 프로미스 중에서 가장 먼저 값을 리턴하는 아이만 출력된다.
async function pickFruits() {
return Promise.race([getApple(), getBanana()]));
}
return
처리를 제대로 해주지 못했을 때 promise
도 똑같이 promise HELL
이 발생하게 된다.
const printAll = () => {
gotoCodestates()
.then(data => {
sitAndCode()
.the(data => {
getoBed()
.the(data => {
....!!!!!!!!
})
})
})
}
resolve, reject 를 사용하지 않고 rerturn 하면 PromiseState 값이 출력된다.
JavaScript는 ES8에서 async/await키워드를 제공하여 복잡한 Promise 코드를 간결하게 작성할 수 있게 되었다. 함수 앞에 async
키워드와 함수 내에 await
키워드를 반드시 작성해주어야 하며 await
키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 된다.
// 함수 선언식
async function funcDeclarations() {
await 작성하고자 하는 코드
...
}
// 함수 표현식
const funcExpression = async function () {
await 작성하고자 하는 코드
...
}
// 화살표 함수
const ArrowFunc = async () => {
await 작성하고자 하는 코드
...
}
함수 앞에 async
키워드를 사용하면 코드 블럭이 자동으로 Promise를 생성한다.
async function getApple() {
await delay(1000)
return '사과';
}
async function getBanana() {
await delay(1000)
return '바나나';
}
async function pickFruits() {
const apple = await getApple();
const apple = await getBanana();
return `${apple} ${banana}`
}
pickFruits().then(console.log)
async/await 예제로 위 코드의 pickFruits 함수를보면 await를 통해 getApple() 가 다 수행되어야 디음 getBanana() 가 시작될 수 있다. 하지만 위와 같이 코드를 작성한다면 getApple()과 getBanana() 받아오는 것은 서로 연관이 없는데 기다려야 하니까 비효율적일 수 있다.
때문에 이를 아래 코드와 같이 개선할 수 있다. 이처럼promise를 생성하면 안에 getApple() 과 getBanana() 가 바로 실행하기 때문에 병렬적으로 실행될 수 있다.
async function pickFruits() {
const applePromise = getApple();
const bananaPromise = getBanana();
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} ${banana}`
}