자바스크립트에서의 비동기 처리란 어떤 기능이 실행되고 있는 동안 다른 동작을 멈추지 않고 실행하는것을 의미한다.
function makeCoffee(order){
setTimeout(()=>{
console.log(`make ${order}☕️`);
},
Math.floor(Math.random() * 100) + 1) // 임의의 값 생성
}
function getCoffee(){
makeCoffee('americano');
makeCoffee('cafe latte');
makeCoffee('cold brew');
}
getCoffee(); // 출력결과는 어떻게 되는가 ?
위 코드를 비동기로 실행시켰지만 랜덤한 지연시간값을 생성하므로 순서대로 실행시키지는 않는다. 순서를 제어하고 싶다면 어떻게 해야 할까 ? 콜백함수로 비동기 실행을 구현하면 다음과 같이 만들 수 있다.
function makeCoffee(order, callback){
setTimeout(()=>{
console.log(`make ${order}☕️`);
callback();
},
Math.floor(Math.random() * 100) + 1) // 임의의 값 생성
}
function getCoffee(){
makeCoffee('americano', ()=>{
makeCoffee('cafe latte', ()=>{
makeCoffee('cold brew', ()=>{
console.log('End');
});
});
});
}
getCoffee(); // 동작의 순서를 제어할 수 있다.
위 코드에서는 다음에 실행할 함수를 콜백함수로 넘겨주어 순서를 제어할 수 있다. 그러나 다음과 같은 문제가 있다.
위의 경우에서는 3개의 함수만 실행시켰지만 그보다 함수를 더 많이 실행시켜야 한다면 계속 들여쓰기를 해가면서 함수를 콜백으로 넘겨주어야 할 것이고 그렇게 코드를 작성하면 가독성 이 낮아질 뿐 아니라 코드를 관리하기 힘들어진다. 이 문제를 해결하기위해 promise
객체를 사용한다.
promise
객체를 통해 앞에서 살펴본 콜백지옥을 해결할 수 있다. promise
객체는 비동기 작업이 맞이할 완료 또는 실패와 그 결과 값을 나타낸다. promise
는 세가지 상태를 가지며 각각의 상태의 종류는 다음과 같다.
아래의 코드를 살펴보자.
// promise 생성, 이때 상태는 대기상태
// 이 구문을 생산자(Producer)라고 한다.
const promise = new Promise((resolve, reject)=>{
console.log('promise is running...');
// 어떤 작업을 실행한 후
somethingWork((error, data)=>{
// 실행한 결과가 error 값이라면 reject 실행, 이때 상태가 rejected 로 변한다
if(error){
reject(error);
}
// 이때 상태는 fulfilled 로 변한다
else{
resolve(data);
}
});
});
// 생성한 promise 를 사용한다
// 이 구문은 소비자(Consumer)라고 한다.
// then, catch, finally 를 사용할 수 있다.
promise.then( data => {
// is Resolved
// 이 구문은 연산이 성공적으로 실행되었을때 resolve 함수를 통해 실행된다.
return data;
})
// 위 연산에서 data를 리턴받아 다른 처리를 할 수 있다.
.then( data =>{
process(data);
})
.catch( error => {
// is Error
// 이 구문은 prmoise 가 실해하면 reject 함수를 통해 실행된다.
}).finally(()=>{
// is finished
// 이 구문은 promise가 성공하든 실패하든 결과에 상관없이 실행된다.
});
그러나 Promise
또한 콜백 지옥과 같은 구조로 될 가능성이 있다. 예를 들면 then
실행절에서 또 새로운 프로미스를 만들거나 then
절이 길어진다면 다시 콜백 지옥과 비슷한 상황에 마주하게 된다. 이와같은 문제를 해결하기 위해 Async
, Await
가 등장했다. 이를 사용하면 동기식으로 프로그래밍을 하는것 처럼 코드를 작성할 수 있다.
function makeCoffee(order){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
let coffee = `make ${order}☕️`;
console.log(coffee);
resolve(coffee);
},
Math.floor(Math.random() * 100) + 1) // 임의의 값 생성
});
}
async function asyncGetCoffee(){
// 동기적으로 실행결과를 얻을 수 있다.
let americano = await makeCoffee('americano');
let cafeLatte = await makeCoffee('cafe latte');
let coldBrew = await makeCoffee('cold brew');
}
asyncGetCoffee();
Async & Await
은 promise
를 대체하여 간단하게 표시하는 것처럼 기존에 존재하는 문법위에, 또는 감싸서 편하게 사용할 수 있는 키워드를 가르켜 Syntactic Suger라 부른다.
여러개의 비동기 함수를 실행하고 차례대로 실행에 대한 값을 일괄적으로 처리하거나 제일먼저 끝난 처리에 대한 결과를 반환하려면 Promise.all()
, Promise.race()
를 사용할 수 있다.
function runningMan(name){
let runner = name;
let runtime = Math.floor(Math.random() * 500); // 임의의 값 생성
return new Promise((resolve, reject) =>{
setTimeout(()=>{
resolve({runner, runtime});
},
runtime)
});
}
function startRace(){
let one = runningMan('One');
let two = runningMan('Two');
let three = runningMan('Three');
let four = runningMan('Four');
// 가장 먼저 실행이 완료된 동작에 대한 결과만 실행
Promise.race([one, two, three, four]).then(({runner, runtime}) =>{
console.log(`${runner} is come in first : ${runtime}`);
});
}
function waitAllRunner(){
let one = runningMan('One');
let two = runningMan('Two');
let three = runningMan('Three');
let four = runningMan('Four');
// 모든 실행이 완료된 결과를 순서대로 반환 한다.
Promise.all([one, two, three, four]).then((values) =>{
values.forEach(({runner, runtime})=>{
console.log(`${runner} : ${runtime}`);
});
});
}