개발자는 코드 작성시 예측 가능한 코드를 작성하도록 노력해야 한다.
그러기 위해서는 비동기로 작동하는 코드를 제어할 수 있는 방법에 대해 잘 알고 있어야 한다.
여러 방법 중 하나는 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();
console.log(
`아래와 같이 Callback 함수를 통해 비동기 코드의 순서를 제어할 수 있습니다!`
);
Callback
함수를 통해 비동기 코드의 순서를 제어할 수 있지만 코드가 길어질 수록 복잡해지고 가독성이 낮아지는 Callback Hell이 발생하는 단점이 있다.
const printString = (string, callback) => {
setTimeout(function () {
console.log(string);
callback();
}, Math.floor(Math.random() * 100) + 1);
};
const printAll = () => {
printString('A', () => {
printString('B', () => {
printString('C', () => {
printString('D', () => {
printString('E', () => {
printString('F', () => {
printString('G', () => {
printString('H', () => {
printString('I', () => {
printString('J', () => {
printString('K', () => {
printString('L', () => {
printString('M', () => {
printString('N', () => {
printString('O', () => {
printString('P', () => {});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
};
printAll();
Promise
(프로미스)Promise
를 활용하는 방법이다.Promise
는 class이기 때문에 new
키워드를 통해 Promise
객체를 생성한다.
또한 Promise
는 비동기 처리를 수행할 콜백 함수(executor)를 인수로 전달받는데 이 콜백 함수는 resolve
, reject
함수를 인수로 전달받는다.
Promise
객체가 생성되면 콜백함수는 자동으로 실행되고 작성했던 코드들이 작동된다.
코드가 정상적으로 처리가 되었다면 resolve
함수를 호출하고 에러가 발생했을 경우에는 reject
함수를 호출하면 된다.
let promise = new Promise((resolve, reject) => {
// 1. 정상적으로 처리되는 경우
// resolve의 인자에 값을 전달할 수도 있다.
resolve(value);
// 2. 에러가 발생하는 경우
// reject의 인자에 에러메세지를 전달할 수도 있다.
reject(error);
});
Promise
객체의 내부 프로퍼티new Promise
가 반환하는 Promise
객체는 state
, result
내부 프로퍼티를 갖는다..then
, .catch
, .finally
의 메서드를 사용해야 접근이 가능하다.State
pending
(대기)이다.executor
)가fulfilled
(이행)로 변경이 되고,rejected
(거부)가 된다.Result
undefined
이다.executor
)가resolve
(value)가 호출되면 value
로,reject
(error)가 호출되면 error
로 변합니다..Then
executor
에 작성했던 코드들이 정상적으로 처리가 되었다면 resolve
함수를 호출하고 .then
메서드로 접근할 수 있다.
또한 .then
안에서 리턴한 값이
Promise
면 Promise
의 내부 프로퍼티 result
를 다음 .then
의 콜백 함수의 인자로 받아오고,
Promise
가 아니라면 리턴한 값을 .then
의 콜백 함수의 인자로 받아올 수 있다.
let promise = new Promise((resolve, reject) => {
resolve("성공");
});
promise.then(value => {
console.log(value);
// "성공"
})
.Catch
executor
에 작성했던 코드들이 에러가 발생했을 경우에는 reject
함수를 호출하고 .catch
메서드로 접근할 수 있다.
let promise = new Promise(function(resolve, reject) {
reject(new Error("에러"))
});
promise.catch(error => {
console.log(error);
// Error: 에러
})
.Finally
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
로 작성된 아래의 코드
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = () => {
printString('A')
.then(() => {
return printString('B');
})
.then(() => {
return printString('C');
});
};
printAll();
console.log(
`아래와 같이 Promise를 통해 비동기 코드의 순서를 제어할 수 있습니다!`
);
Promise.all()
: 여러 개의 비동기 작업을 동시에 처리하고 싶을때 사용const promiseOne = () => new Promise((resolve, reject) => setTimeout(() => resolve('1초'), 1000));
const promiseTwo = () => new Promise((resolve, reject) => setTimeout(() => resolve('2초'), 2000));
const promiseThree = () => new Promise((resolve, reject) => setTimeout(() => resolve('3초'), 3000));
Promise.all()
은 여러 개의 비동기 작업을 동시에 처리하고 싶을때 사용한다.
인자로는 배열을 받는다. 해당 배열에 있는 모든 Promise
에서 executor
내 작성했던 코드들이 정상적으로 처리가 되었다면 결과를 배열에 저장해 새로운 Promise
를 반환 해준다.
앞서 배운 Promise chaining을 사용했을 경우는 코드들이 순차적으로 동작되기 때문에 총 6초의 시간이 걸리게 되고, 같은 코드가 중복되는 현상도 발생하게 된다.
// 기존
const result = [];
promiseOne()
.then(value => {
result.push(value);
return promiseTwo();
})
.then(value => {
result.push(value);
return promiseThree();
})
.then(value => {
result.push(value);
console.log(result);
// ['1초', '2초', '3초']
})
Promise.all()
은 비동기 작업들을 동시에 처리하기 때문에, 3초 안에 모든 작업이 종료된다.// promise.all
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
.then((value) => console.log(value))
// ['1초', '2초', '3초']
.catch((err) => console.log(err));
Promise.all()
인자로 받는 배열에 있는 Promise
중 하나라도 에러가 발생하게 되면 나머지 Promise
state와 상관없이 즉시 종료된다.Error: 에러1
이 반환된 후로는 더 이상 작동하지 않고 종료된다.Promise.all([
new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러1'))), 1000),
new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러2'))), 2000),
new Promise((resolve, reject) => setTimeout(() => reject(new Error('에러3'))), 3000),
])
.then((value) => console.log(value))
.catch((err) => console.log(err));
// Error: 에러1
Promise
를 통해 비동기 코드의 순서를 제어할 수 있지만 Callback
함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생하는 단점이 있다.
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = () => {
printString('A').then((value) => {
console.log(value);
printString('B').then((value) => {
console.log(value);
printString('C').then((value) => {
console.log(value);
printString('D').then((value) => {
console.log(value);
printString('E').then((value) => {
console.log(value);
printString('F').then((value) => {
console.log(value);
printString('G').then((value) => {
console.log(value);
printString('H').then((value) => {
console.log(value);
printString('I').then((value) => {
console.log(value);
printString('J').then((value) => {
console.log(value);
printString('K').then((value) => {
console.log(value);
printString('L').then((value) => {
console.log(value);
printString('M').then((value) => {
console.log(value);
printString('N').then((value) => {
console.log(value);
printString('O').then((value) => {
console.log(value);
printString('P').then((value) => {
console.log(value);
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
};
printAll();
console.log(
`아래와 같이 Promise를 통해 비동기 코드의 순서를 제어할 수 있지만 Callback 함수와 같이 코드가 길어질수록 복잡해지고 가독성이 낮아지는 Promise Hell이 발생하는 단점이 있다.`
);
Async
/Await
JavaScript는 ES8에서 async
/await
키워드를 제공하여 복잡한 Promise
코드를 간결하게 작성할 수 있게 되었다.
함수 앞에 async
키워드를 사용하고 async
함수 내에서만 await
키워드를 사용하면 된다.
이렇게 작성된 코드는 await
키워드가 작성된 코드가 동작하고 나서야 다음 순서의 코드가 동작하게 된다.
// 함수 선언식
async function funcDeclarations() {
await 작성하고자 하는 코드
...
}
// 함수 표현식
const funcExpression = async function () {
await 작성하고자 하는 코드
...
}
// 화살표 함수
const ArrowFunc = async () => {
await 작성하고자 하는 코드
...
}
const printString = (string) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
console.log(string);
}, Math.floor(Math.random() * 100) + 1);
});
};
const printAll = async () => {
await printString('A');
await printString('B');
await printString('C');
};
printAll();
console.log(
`Async/Await을 통해 Promise를 간결한 코드로 작성할 수 있게 되었습니다.`
);