1. callback 지옥을 벗어나자!
- 콜백 함수는 아래처럼 나중에 실행할 함수!
const 버튼 = document.querySelector('.button'); 버튼.addEventListener('click', function(){}); // 버튼.addEventListener('click', '다른 곳에서 짠 함수이름'); // ex('click', helloworld100) // function helloworld100() { // console.log('hello world'); // console.log('hello world'); // } // 버튼.addEventListener('click', ()=>{});
function 함수하나(출력){ console.log('hello'); 출력('world'); } 함수하나(console.log);
- 위에 코드가 작성이 되는 이유는 함수나 메서드는 결국 어떤 기능을 가리키고 있는 식별자이기 때문
let hojun = console.log; hojun('hello world');
- 함수 하나가 로그인 기능이었고, 이 로그인 기능 이후에 계좌 연동, 이미지 로드 등의 기능이 실행되어야 한다면 아래와 같이 여러 함수들의 중첩이 일어남
function 로그인(){ if (로그인성공) { 이미지로드(); 계좌연동(...계좌연동 이후 일어나는 코드...); ... } }
- 이렇게 연결된 것을 아래처럼 풀어낼 수 있음
user.login( id, pw, 로그인성공, 로그인실패, 이미지로드, 계좌연동...)
- 여기서는 콜백지옥(callback hell)이 잘 감이 안오지만 아래처럼 실제 코드로 짜보면 감이옴.
userData.login( id, pw, (user) => { userData.getData( user, (userData) => { ..콜백에 콜백.. }, (fail) => { ..콜백에 콜백.. } ); }, (fail) => { ..콜백에 콜백.. }, (user) => { //이미지 로드 ..콜백에 콜백.. }, (user) => { // 계좌 연동 ..콜백에 콜백.. }, );
- 콜백 지옥 코드 체험(모던자바스크립트 예제)
2. Promise
- 이러한 콜백 지옥을 탈출할 수 있게 만들어주는 것이 promise!
- promise는 언제 내가 널(콜백함수) 다시 불러줄지 모르겠지만, 언젠가 널 다시 불러주겠다 약속하겠다는 뜻
- 노드를 하실 것이라면 반드시 알아야 함!
- (2022년에는 대부분) (promise를 여러개 실행할 수 있는) Promise.all보다는 allSettled(실패한 것만 추려내는 기능이 있음)를사용할 것으로 보임
let p = new Promise(function(resolve, reject) { // 실행코드 }); // resolve(value) — 작업이 성공적으로 마무리되면 호출, 결과는 value에 담김 // reject(error) — 작업이 실패시 호출, error는 error에 담김
// 쉬운 예제 let p = new Promise(function(resolve, reject) { resolve('hello world'); }).then(메시지 => { alert(메시지); return 메시지.split(' ')[0] }).then(메시지 => { alert(메시지); return 메시지[0] }).then(메시지 => { alert(메시지); }); let p = new Promise(function(resolve, reject) { // resolve('hello world'); reject('hello world'); }).then(메시지 => { alert(메시지); return 메시지.split(' ')[0] }).then(메시지 => { alert(메시지); return 메시지[0] }).then(메시지 => { alert(메시지); }).catch(메시지 => { alert('catch 실행!! :' + 메시지); }); let p = new Promise(function(resolve, reject) { // resolve('hello world'); reject('hello world'); }).then(메시지 => { alert(메시지); return 메시지.split(' ')[0] }).then(메시지 => { alert(메시지); return 메시지[0] }).then(메시지 => { alert(메시지); }).catch(메시지 => { alert('catch 실행!! :' + 메시지); }); let p = new Promise(function(resolve, reject) { // resolve('hello world'); reject('hello world'); }).then(메시지 => { alert(메시지); throw Error("에러 발생!") return 메시지.split(' ')[0] }).then(메시지 => { alert(메시지); return 메시지[0] }).then(메시지 => { alert(메시지); }).catch(메시지 => { alert('catch 실행!! :' + 메시지); }); let p = new Promise(function(resolve, reject) { // resolve('hello world'); // reject('hello world'); resolve('hello world'); }).then(메시지 => { alert(메시지); throw Error("에러 발생!") return 메시지.split(' ')[0] }).then(메시지 => { alert(메시지); return 메시지[0] }).then(메시지 => { alert(메시지); }).catch(메시지 => { alert('catch 실행!! :' + 메시지); });
- console.log로 promise를 찍어보세요.
- 왜 하냐고요? 비동기가 핵심입니다.
- 성공과 실패만 합니다. 중립은 없습니다. 대기(pending)는 있습니다.
// 모던자바스크립트 예제 let promise = new Promise(function(resolve, reject) { // 프라미스가 만들어지면 executor 함수는 자동으로 실행됩니다. // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 'done'이 됩니다. setTimeout(() => resolve("끝남!"), 1000); }); console.log('hello world'); console.log(promise);
// 모던자바스크립트 예제 (살짝 수정) let promise = new Promise(function(resolve, reject) { // 프라미스가 만들어지면 executor 함수는 자동으로 실행됩니다. // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 'done'이 됩니다. setTimeout(() => resolve("이제야 끝남!"), 10000); }); console.log('hello world'); console.log(promise);
let promise = new Promise(function(resolve, reject) { // 프라미스가 만들어지면 executor 함수는 자동으로 실행됩니다. // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result는 'done'이 됩니다. setTimeout(() => resolve(console.log('데이터를 성공적으로 받아옴')), 1000); }); console.log('hello world');
- 일부러 에러를 던져줄 수도 있습니다.
// 모던자바스크립트 예제 (살짝 수정) let promise = new Promise(function(resolve, reject) { // 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냅니다. setTimeout(() => reject("에러에러!!"), 3000); });
let promise = new Promise(function(resolve, reject) { // 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냅니다. setTimeout(() => reject(new Error("에러 발생!")), 3000); });
// 실행하지 마세요. pending에 빠집니다. let promise = new Promise(function(resolve, reject) { console.log('hello world') });
- 전체적인 모습은 아래와 같습니다. 실행이 되는 코드는 Node에서 살펴보도록 하겠습니다.
new Promise((resolve, reject) => {...code...}) .then(...code...) .then(...code...) .finally(...code...) .catch(...code...); // <-- .catch에서 에러 객체를 다룰 수 있음
- 아래 코드를 콘솔에서 실행해보세요.
// 모던자바스크립트 예제 new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); // (*) }).then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; });
// 모던자바스크립트 예제 (살짝 수정) new Promise(function(resolve, reject) { setTimeout(() => reject('error'), 1000); }).then(function(result) { alert(result + ' : 잘 수행!'); return result + 'one'; }).catch(function(result) { alert(result + ' : 애러 발생!'); // 1 return result + 'two'; }).then(function(result) { alert(result + ' : 잘 수행!'); // 2 return result + 'three'; });
// 모던자바스크립트 예제 let p = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 10000); // (*) }); console.log('hello world'); let p2 = p.then(function(result) { // (**) console.log(result); // 1 return result * 2; }); console.log('hello world2'); let p3 = p2.then(function(result) { // (***) console.log(result); // 2 return result * 2; }); console.log('hello world3'); let p4 = p3.then(function(result) { console.log(result); // 4 return result * 2; });
- 다음 예제는 프라미스 체이닝이 아님!
// 모던자바스크립트 예제 let promise = new Promise(function(resolve, reject) { setTimeout(() => resolve(1), 1000); }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; }); promise.then(function(result) { alert(result); // 1 return result * 2; });
- 모던 자바스크립트의 콜백함수 애러처리
// 모던자바스크립트 // 정리 전 loadScript('1.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('2.js', function(error, script) { if (error) { handleError(error); } else { // ... loadScript('3.js', function(error, script) { if (error) { handleError(error); } else { // 모든 스크립트가 로딩된 후, 실행 흐름이 이어집니다. (*) } }); } }) } }); // 정리 후 loadScript("/article/promise-chaining/one.js") .then(script => loadScript("/article/promise-chaining/two.js")) .then(script => loadScript("/article/promise-chaining/three.js")) .then(script => { // 스크립트를 정상적으로 불러왔기 때문에 스크립트 내의 함수를 호출할 수 있습니다. one(); two(); three(); });
3. 실전예제
- 아래 예제를 확인해보면 1과 2가 출력되고 나중에 console.log가 출력이 되기 때문에 비동기라는 것을 알 수가 있습니다.
fetch('https://raw.githubusercontent.com/paullabkorea/coronaVaccinationStatus/main/data/data.json') .then(function(response) { return response.json(); }) .then(function(json) { console.log(json); return json }) console.log(1); console.log(2);
- 실전예제
fetch('https://raw.githubusercontent.com/paullabkorea/coronaVaccinationStatus/main/data/data.json') .then(function(response) { console.log(1); return response.json(); }) .then(function(json) { console.log(2); console.log(json); return json }) .then(function(json) { console.log(3); console.log(json.filter(s => s['시·도별(1)'] === '전국')); return })
// 1차 접종 퍼센트를 구해주세요! fetch('https://raw.githubusercontent.com/paullabkorea/coronaVaccinationStatus/main/data/data.json') .then(function(response) { console.log(1); return response.json(); }) .then(function(json) { console.log(2); console.log(json); return json }) .then(function(json) { console.log(3); console.log(json.filter(s => s['시·도별(1)'] === '전국').map(obj => obj['1차 접종 퍼센트'])); return })
fetch('https://raw.githubusercontent.com/paullabkorea/coronaVaccinationStatus/main/data/data.json') .then(function(response) { console.log(1); throw Error('애러났어유!') return response.json(); }) .then(function(json) { console.log(2); console.log(json); return json }) .then(function(json) { console.log(3); console.log(json.filter(s => s['시·도별(1)'] === '전국')); return }) .catch(err => alert(err))
4. async, await(에이씽크, 어웨잇)
- Ajax도 에이작스죠? 이거 어싱크라고 하시는 분이 많습니다.
- 일부 런타임에서는 async 없이도 await을 사용할 수 있습니다. 앞으로는 다른 런타임도 그렇게 될 것이고요. (top level await)
// 모던 자바스크립트 예제 async function f() { return 100; } f().then(alert); // 100
// 모던 자바스크립트 예제 (살짝 수정) async function f() { return 100; } f().then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; });
- await은 (크롬을 제외한 브라우저의 런타임) 일반 함수에서는 사용이 불가합니다.
// 모던 자바스크립트 예제 async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("완료!"), 1000) }); let result = await promise; // 프라미스가 이행될 때까지 기다림 (*) alert(result); // "완료!" } f();
- 시간을 3초로 하여 어떤 결과가 나오는지 봅시다.
// 모던 자바스크립트 예제 (살짝 수정) async function f() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("완료!"), 3000) }); let result = await promise; // 프라미스가 이행될 때까지 기다림 (*) alert(result); // "완료!" return 100 } f().then(function(result) { // (**) alert(result); // 1 return result * 2; }).then(function(result) { // (***) alert(result); // 2 return result * 2; }).then(function(result) { alert(result); // 4 return result * 2; });
// 모던 자바스크립트의 잘못된 예제 (https://ko.javascript.info/async-await) // 보라님에게 제보해주세요.ㅎㅎ 시간이 지나면서 보완이 되어벼러서 잘못된 내용이 된 것입니다. let response = await fetch('https://raw.githubusercontent.com/paullabkorea/coronaVaccinationStatus/main/data/data.json'); let data = await response.json();
- 추가된 문법
- top-level-await
- for await (변수 of 프로미스배열) 문법
5. 호출스택
- js 코드
function one(){ two(); } function two(){ three(); } function three(){ console.log('end'); }
- (호출 스택) 쌓이면서 하나씩 빠짐
three() two() one() anonymous // 가상 전역 컨텍스트, 이것도 마지막에 빠짐
- 그러면 아래 코드는?
console.log(1); setTimeout(function(){ console.log(2); }, 1000) // setTimeout(()=> console.log(2), 1000) console.log(3);
- 호출스택이 모두 끝나야 백그라운드 실행합니다. 중간에 실행되는 경우 없어요.
- 이벤트 루프를 설명하는 영상은 매우 많고, 설명이 조금씩 다릅니다. 이유는 대부분이 복잡한 개념을 단순화 해서 설명하고 있기 때문이에요.
- 저는 'Node.js 교과서' 기준으로 설명으로 설명해드리도록 하겠습니다.

console.log(1); setTimeout(실행, 1000) console.log(3); function 실행(){ console.log(2); }
- 메모리에 실행함수 적재
- 호출스택에 anonymous 들어감
- console.log(1)이 호출 스택에 쌓임
- console.log(1)이 실행되어 console에 1을 찍고 스택에서 사라짐
- 백그라운드에 timer(실행, 1) (계속 시간을 카운팅하는 중)
- console.log(3)이 스택에 쌓임
- console.log(3)이 실행되어 console에 3를 찍고 스택에서 사라짐
- anonymous 사라짐
- 1초가 지나가면 백그라운드에서 태스크 큐로 '실행' 함수를 가져옴(백그라운드에서 없어짐)
- 태스크 큐에서 실행함수를 호출 스택으로 가져옴
- '실행'함수 위에 console.log(2)가 쌓임
- console.log(2)이 실행되어 console에 2를 찍고 스택에서 사라짐
- '실행' 함수가 호출 스택에서 사라짐
참고영상
(실습) 실전 로그인 페이지 만들기!
[ '{{repeat(10, 7)}}', { _id: '{{objectId()}}', id: '{{firstName()}}', pw: '{{guid()}}', index: '{{index()}}', picture: 'http://placehold.it/32x32', age: '{{integer(20, 40)}}', eyeColor: '{{random("blue", "brown", "green")}}', name: '{{firstName()}} {{surname()}}', gender: '{{gender()}}', company: '{{company().toUpperCase()}}', email: '{{email()}}', phone: '+1 {{phone()}}', address: '{{integer(100, 999)}} {{street()}}, {{city()}}, {{state()}}, {{integer(100, 10000)}}', grade: '{{random("새싹", "일반", "실버", "골드", "다이아몬드", "관리자")}}' } ]