Callback 함수란?
함수를 하나의 파라미터 인자로 전달하는데, 바로 실행되는게 아닌, 특정한 시점에 호출되는 함수를 말한다.
보통 콜백 함수는 함수의 매개변수로 전달하여 특정 시점에서 콜백 함수를 호출한다.
synchronous vs Asynchronous
- 자바스크립트는 synchronous (동기적)이다.
- 호이스팅(hoisting)이 된 이후부터, 코드가 작성한 순서에 맞춰서 하나하나 동기적으로 실행된다.
- asynchronous 란, 비 동기적으로 언제 코드가 실행될지 예측할 수 없는 것을 말한다.
// asynchronous의 예 // web api 중 setTimeout() => 지정한 시간이 지나면 지정한 콜백함수를 호출 해주는 api console.log('1'); setTimeout(function() { console.log('2'); }, 1000); console.log('3') // 1 -> 3 -> 2
Synchronous callback
즉각적으로 실행되는 콜백 함수 (동기 콜백)
function printImmediately (print) { print(); // print 함수 즉시 호출 } // function hoisting printImmediately(()=> console.log('hello'));
Asynchronous callback
언제 호출될지 알 수 없는 콜백 함수, 특정 시점에 호출 되는 함수 (비동기 콜백)
function printWithDelay(print, timeout){ setTimeout(print, timeout); // 일정 시간 이후 인자로 받은 print 함수 호출 } printWithDelay(()=> console.log('async callback'), 2000); // 2초후 async callback 출력
콜백 지옥!
class UserStorage { loginUser(id, password, onSuccess, onError) { setTimeout(() => { if ( (id === "dylan" && password === "test") || (id === "coder" && password === "test") ) { onSuccess(id); // onSuccess() callback 호출 } else { onError(new Error("not found")); // onError() callback 호출 } }, 2000); } getRoles(user, onSuccess, onError) { setTimeout(() => { if (user === "dylan") { onSuccess({ name: "dylan", role: "admin" }); //onSuccess() callback 호출 } else { onError(new Error("no access")); // onError() callback 호출 } }, 1000); } } // 1. id, password 를 받아옴 // 2. 로그인이 성공적으로 됨 // 3. 로그인에 성공한 아이디를 받아와서 서버에 역할 요청 const userStorage = new UserStorage(); const id = prompt("enter your id"); const password = prompt("enter password"); userStorage.loginUser( id, password, (user) => { //onSuccess 롤백 함수 userStorage.getRoles( user, (userWithRole) => { alert( `hello ${userWithRole.name}, you have a ${userWithRole.role} role` ); }, (error) => { //onError 롤백 함수 console.log(error); } ); }, (error) => { console.log(error); } );
- 콜백 지옥에서의 문제점
- 콜백을 이용해서, 콜백 함수안에서 다른 것을 호출하고 그 안에서 또 다른 콜백을 호출하는 등.. 가독성이 너무 떨어짐
- 비즈니스 로직을 한눈에 이해하기 어려움
Promise
- 자바스크립트에서 제공하는 비동기를 간편하게 처리할 수 있도록 도와주는 오브젝트이다.
- 정해진 시간동안 기능을 수행하고 나서, 정상적으로 기능을 수행했다면 성공한 결과값을, 예상치 못한 문제가 발생했다면 에러를 전달해준다.
- Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
- Fullfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과값을 반환해준 상태
- Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
Pending(대기)
먼저 아래와 같이 new Promise()
메서드를 호출하면 대기(Pending) 상태가 됨
new Promise();
new Promise()
메서드를 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve
, reject
를 사용한다.
const promise = new Promise(function(resolve, reject) { // doing some heavy work(network, read files) console.log('doing something..'); //실행해보면 바로 resolve가 동작하는걸 알수있다. // Promise를 호출하는 순간 resolve 처리됨을 알수있음. });
new Promise(function(resolve, reject) { // ... });
Fulfilled(이행)
여기서 콜백 함수의 인자 resolve
를 아래와 같이 실행하면 이행(Fulfilled) 상태가 된다.
new Promise(function(resolve, reject) { resolve(); });
그리고 이행 상태가 되면 아래와 같이 then()
을 이용하여 처리 결과 값을 받을 수 있음.
function getData() { return new Promise(function(resolve, reject) { var data = 100; resolve(data); }); } // resolve()의 결과 값 data를 resolvedData로 받음 getData().then(function(resolvedData) { console.log(resolvedData); // 100 });
Rejected(실패)
new Promise()
로 프로미스 객체를 생성하면 콜백 함수 인자로 resolve
와 reject
를 사용할 수 있다. 여기서 reject
를 아래와 같이 호출하면 실패(Rejected) 상태가 된다.
new Promise(function(resolve, reject) { reject(); });
function getData() { return new Promise(function(resolve, reject) { reject(new Error("Request is failed")); }); } // reject()의 결과 값 Error를 err에 받음 getData().then().catch(function(err) { console.log(err); // Error: Request is failed });
그리고, 실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()
로 받을 수 있다.
// Promise is a JavaScript object for asynchronous operation // State : pending -> fulfilled or rejected // Producer vs Consumer // 1. Producer // When new Promise is created, the executor runs automaticailly. (새로운 promise가 만들어졌을때, executor가 자동으로 실행됨) const promise = new Promise((resolve, reject) => { // doing some heavy work (네트워크에서 데이터를 받아오거나, 파일에서 큰 데이터를 읽어오는등의 시간이 걸리는 일은 비동기식으로 처리한다. 받아오는 동안 다음 라인의 코드가 실행되지 않기 때문) // network, read files.. console.log("doing something..."); // doing something... (promise가 만들어지는 순간 실행) setTimeout(() => { resolve("Dylan"); //네트워크로부터 파일 받아오는걸 성공했을때 resolve라는 콜백함수를 호출(파라미터로 Dylan 전달) // reject(new Error("no network")); //실패했을때 에러 콜백함수 }, 2000); }); // 2. Consumers : then, catch, finally promise .then((value) => { console.log(value); }) ////값이 정상적으로 잘 수행이 되었다면, value를 받아와서 원하는 함수 실행 (여기서 value는 'Dylan') .catch((error) => { console.log(error); }) .finally(() => { console.log("finally"); // 성공했던 실패했던 상관없이 무조건 마지막에 실행 });
Promise chaining
const fetchNumber = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }); fetchNumber .then((num) => num * 2) // 2 .then((num) => num * 3) // 6 .then((num) => { return new Promise((resolve, reject) => { //return 값으로 새로운 promise 를 넘기는것도가능 setTimeout(() => { resolve(num - 1); // 5 }, 1000); }); }) .then((num) => console.log(num)); // 2초후 5 출력
Error Handling
const getHen = () => new Promise((resolve, reject) => { setTimeout(() => resolve("🐔"), 1000); }); const getEgg = (hen) => new Promise((resolve, reject) => { // setTimeout(() => resolve(`${hen} => 🥚`), 1000); setTimeout(() => reject(new Error(`error! `)), 1000); }); const cook = (egg) => new Promise((resolve, reject) => { setTimeout(() => resolve(`${egg} => 🍳`), 1000); }); getHen() .then(getEgg) // 받아오는 리턴값을 그대로 전달할때는 생략가능 .then((hen) => getEgg(hen))와 같음 .catch((error) => { return "🐥"; //getEgg에서 값을 얻어오는걸 실패했을때 에러 처리, 뒤에 문장들은 실행됨 }) // 에러 핸들링 (getEgg가 실패했을시, 🐥로 대체) .then(cook) //.then((egg) => cook(egg)) .then(console.log) //.then((meal) => console.log(meal)); //🐥 -> 🍳 .catch(console.log);
앞선 콜백 지옥 예제 해결하기
class UserStorage { loginUser(id, password) { return new Promise((resolve, reject) => { setTimeout(() => { if ( (id === "dylan" && password === "test") || (id === "coder" && password === "test") ) { resolve(id); } else { reject(new Error("not found")); } }, 2000); }); } getRoles(user) { return new Promise((resolve, reject) => { setTimeout(() => { if (user === "dylan") { resolve({ name: "dylan", role: "admin" }); } else { reject(new Error("no access")); } }, 1000); }); } } const userStorage = new UserStorage(); const id = prompt("enter your id"); const password = prompt("enter password"); userStorage .loginUser(id, password) .then(userStorage.getRoles) .then((user) => alert(`hello ${user.name}, you have a ${user.role} role`)) .catch(console.log);