callback function
: 함수가 인자로 다른 함수를 전달하는 것을 의미한다.
function greeting(name){
alert('Hello ' + name);
}
function userInput(callback) { // 인자로 다른 함수를 받는다.
let name = prompt('Please enter your name.');
callback(name);
}
userInput(greeting)
위 함수를 실행하면
please enter your name 이라는 입력창이 나와서 name을 입력할 수 있다.
코드에서 보면
let name = prompt('Please enter your name.')
이라는 부분을 실행한 것이다.
그리고 name으로 Kate를 입력하면
callback(name) 즉, greeting(name)을 실행해서
Hello Kate 을 출력하는 것을 확인할 수 있다.
위에서 본 callback의 예제는 syncronous callback으로 즉시 실행된 것을 확인할 수 있다.
하지만 이런 콜백은 하나의 동기적인 동작이 완료되면 계속해서 코드를 실행하는 방식으로 잘 사용되는데 이것이 바로 asyncronous callback 이다.
좋은 예로, 한 promise가 성공하거나 실패한 이후 그 promise 의 마지막에 .then()으로 chaining을 해 줄 수 있는데 이런 구조는 fetch() 와 같은 현대의 web API들에서 널리 쓰인다.
여기서부터는 비동기 흐름을 제어하는 세 가지 중요한 패턴에 대해 공부하려고 한다.
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의 순서가 그냥 랜덤을 나옴
먼저, string 타입을 인자로 받아서 setTimeout을 랜덤으로 실행하는 printString이라는 함수가 있다고 가정한다.
printString() 을 세 번 실행하는 printAll()함수를 실행해 보면 A,B,C가 순서대로 나오지 않고 랜덤한 것을 확인할 수 있었다.
이러한 비동기의 흐름을 제어하는 첫 번째 방법이 callback 이다.
비동기 흐름을 제어하는 세 가지 패턴
1. CALLBACK
const printString = (string, callback) => { // 두 번째 인자로 callback을 받는다.
setTimeout(
() => {
console.log(string)
callback() // 콜백함수를 호출한다.
},
Math.floor(Math.random() * 100) +1
)
}
const printAll = () => {
printString("A", () => { // 'A'를 찍는 이 함수의 콜백은 여기부터 마지막 printString함수까지를 포함한다.
printString("B", () => {
printString("C", () => {})
})
})
}
printAll()
// A,B,C를 순서대로 찍는다.
callback 함수로 비동기를 핸들링할 때의 특징은
- 에러를 다룰 수 있다.
- 에러를 넘겨주는 함수가 있으면 callback을 처리할 때마다 에러를 처리해야 하는 문제가 있다.
- callback hell (혹은 callback pyramid) 가 발생하여 가독성이 떨어진다.
2. PROMISE
const printString = (string) => {
return new Promise((resolve, reject) => { // instance를 리턴한다.
setTimeout(
() => {
console.log(string)
resolve() //promise는 resolve, reject 라는 인자를 받는 callback을 실행한다.
},
Math.floor(Math.random() * 100) +1
)
})
}
const printAll = () => {
printString("A")
.then( () => { // callback을 인자로 받지 않고 .then 으로 이어갈 수 있다.
return printString("B")
})
.then( () => {
return printString("C")
})
}
printAll()
//A,B,C를 순서대로 찍는다.
- 인자로 callback을 받지 않고 promise의 instance를 리턴해준다.
- (promise 자체가 class 같은 거라서 new Promise로 인스턴스를 생성할 수 있다.)
- promise가 resolve, reject 를 인자로 받아서 콜백을 실행한다.
callback 함수를 전달하던 고전적인 방식과는 다른 promise 의 장점
- 기존 callback 하는 방식에서는 err를 넘겨주는 함수가 있을 때, 콜백 처리 시 마다 에러도 처리해 주었는데
promise에서는 .then의 마지막 체인에서 .catch로 핸들링하도록 한다. - 콜백은 자바스크립트 event loop이 현재 실행중인 콜 스택을 완료하기 전에는 절대 호출되지 않는다.
- 비동기 작업이 성공하거나 실패한 뒤에 then()을 이용해 추가한 콜백의 경우에도 현재 실행중인 콜 스택을 완료하기 전에는 절대 호출되지 않는다.
- then()을 이용해 여러개의 콜백을 추가할 수도 있는데 각각의 콜백은 순서대로 실행된다.
- Chaining을 통해 비동기 작업을 순차적으로 실행해야 하는 경우를 제어할 수도 있다.
단점으로는 promise hell이 형성될 수 있다.
예를 들어 아래처럼 promise화된 함수 4개가 있다고 가정한다.
function gotoCodestates() {
return new Promise((resolve, reject) => {
setTimeout( () => { resolve('1. go to Codestates')}, 100)
})
}
function sitAndCode(){
return new Promise((resolve, reject) => {
setTimeout( () => { resolve('2. sit and code')}, 100)
})
}
function getAJob(){
return new Promise((reslove, reject) => {
setTimeout( () => { resolve('3. I got a job!!')}, 100)
})
}
위 함수를 순차적으로 실행하는 코드를 아래와 같이 만들면
gotoCodestates()
.then(data => {
console.log(data)
sitAndCode()
.then(data=> {
console.log(data)
getAJob()
.then(data => {
console.log(data)
})
})
})
promise hell이 발생하여 가독성이 떨어진다.
이런 문제를 수정하기 위해 함수 안에서 함수를 호출하지 않고 리턴하는 방식으로 promise chaining을 할 수도 있다.
- Promise Chaining
gotoCodestates()
.then(data => {
console.log(data)
return sitAndCode(); // return을 통해 다음 비동기를 넘긴다.
})
.then(data => {
console.log(data)
return getAJob();
})
이렇게 promise chianing 방식을 활용하면 콜백도 피할 수 있고 가독성도 좋아진다.
* More about Chaining...
순차적으로 각각의 작업이 이전 단계 비동기 작업이 성공하고 나서 그 결과값을 이용하여 다음 비동기 작업을 실행해야 하는 경우가 있다. 이 때 chaining을 이용하여 해결할 수 있다.
then() 함수는 새로운 promise를 반환하고 각각의 promise는 체인 안에서 서로 다른 비동기 단계의 완료를 나타낸다.
이 때 중요한 것은 반환값이 반드시 있어야 한다는 것이다. 그렇지 않으면 콜백 함수가 이전 promise의 결과를 받을 수 없다.
- Chaining after a catch
chain에서 작업이 실패하더라도 새로운 작업을 수행하는 것이 가능한데 이는 매우 유용하다.
new Promise((resolve, reject) =>
console.log('studying asyncronous');
resolve();
})
.then( () => {
throw new Error('something failed');
console.log('may not be printed');
})
.then( () => {
console.log('Do next, whatever happened before');
});
// output
studying asyncronous
Do next, whatever happened before
// somethid failed 에러가 rejection을 발생시켜서 'may not be printed' 는 출력되지 않았다.
3. ASYNC & AWAIT
*node 버전이 높으면 바로 사용할 수 있다고 한다.
*async function 내부에서만 사용할 수 있다. (즉, async 함수임을 반드시 명시해 주어야 쓸 수 있다.)
Syntax
[returnValue] = await expression;
- expression : Promise 혹은 기다릴 어떤 값
- returnValue : Promise에 의해 만족되는 값. Promise가 아닌 경우네는 그 값 자체를 반환한다.
await 문은 Promise가 fulfill 되거나 reject 될 때까지 async 함수의 실행을 일시정지하고,
- Promise가 fulfill 되면 async 함수를 일시 정시한 부분부터 실행한다.
- Promiserk reject 되면 await문은 reject된 값을 throw한다.
await로 비동기 함수들을 동기적인 프로그래밍인 것처럼 쓸 수 있다. 즉, async await을 이용해 promise를 일반 함수처럼 사용할 수 있다.
위 promise화된 함수들을 다시 예로 들어서,
function gotoCodestates() {
return new Promise((resolve, reject) => {
setTimeout( () => { resolve('1. go to Codestates')}, 100)
})
}
function sitAndCode(){
return new Promise((resolve, reject) => {
setTimeout( () => { resolve('2. sit and code')}, 100)
})
}
function getAJob(){
return new Promise((reslove, reject) => {
setTimeout( () => { resolve('3. I got a job!!')}, 100)
})
}
async과 await을 써서 아래처럼 적어줄 수 있다.
const result = async () => {
const one = await gotoCodestates();
console.log(one)
const two = await sitAndCode();
console.log(two)
const three = await getAJob();
console.log(three)
}
result();
각 함수는 'await' 해 준 부분에서 실행을 잠시 정지하고 기다렸다가 promise가 resolve 되면(result() 를 찍으면) 실행된다.
각 함수의 delay가 gotoCodestates()에서는 100, sitAndCode에서는 300, getAJob()에서는 400으로 지정하더라도 A,B,C를 순서대로 찍는다.
참고해서 볼 자료: 캡틴판교
'TIL ( CODESTATES)' 카테고리의 다른 글
node.js가 어떻게 파일을 읽나 ( fs.readFile / callback, promise, async&await 구현) (0) | 2020.12.17 |
---|---|
promise (0) | 2020.12.13 |
async 공부하기 전 setTimeout , setInterval 이해하기 (0) | 2020.12.12 |
Kahoot Review - Inheritance pattern (0) | 2020.11.19 |
pseudoclassical vs.ES 6 class 인스턴스화와 상속 (sprint) (0) | 2020.11.16 |