Loading the catalog…
Loading the catalog…
JavaScript_Day.9 Day.8의 콜백 함수 다음 단계, Promise와 async/await. "비동기 작업이 끝난 뒤의 결과를 어떻게 받을까"에 대한 두 번째, 세 번째 답을 배운 날이다. 학습 아홉 번째 날이다. 오늘 배운 내용은 이렇다. Promise : 상태(pending, fulfilled, rejected), resolve 와 reject then , catch 와 체이닝 async/await 와 try/catch 콜백, Promise, async/await 비교 이벤트 버블링과 stopPropagation || , && 로 값 고르기 (리액트 문법) map , forEach , filter 1. Promise Promise는 비동기 작업(서버 요청, 타이머 등)의 결과를 나중에 받아서 처리하기 위해 쓰는 객체다. 상태 관리가 명확하고, then 을 이어 붙이는 체이닝 문법 이라 코드 읽기가 편하다. const promise = new Promise((resolve, reject) => { // 성공: resolve(data) → 보통 성공했을 때 호출 // 실패: reject(error) → 실패했을 때 호출 }); new Promise() 의 인자로 비동기 작업을 진행할 콜백 함수를 넣는다. 이 함수를 executor 라고 하고, 매개변수로 받는 resolve 와 reject 가 Promise의 상태를 바꾸는 스위치다. Promise의 세 가지 상태 상태 의미 pending 대기 중. resolve 도 reject 도 아직 호출되지 않은 초기 상태 fulfilled 성공. resolve 가 호출됨 rejected 실패. reject 가 호출됨 pending 상태 확인하기 const promise = new Promise(() => { setTimeout(() => { console.log('hi'); }, 3000); }); console.log(promise); // Promise {<pending>} // (3초 뒤) hi resolve 와 reject 를 받지도, 호출하지도 않았으므로 이 Promise는 영원히 pending 이다. 3초 뒤 hi 가 출력되지만, Promise의 상태는 바뀌지 않는다. resolve로 fulfilled 만들기 const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('hi'); resolve('안녕'); // 성공 상태로 바꾸고, 결과값으로 '안녕'을 전달 }, 3000); }); setTimeout(() => { console.log(promise); }, 3000); // hi // Promise {<fulfilled>: '안녕'} 두 타이머가 모두 3초지만, Promise 안의 타이머가 먼저 등록되어 있어서 먼저 실행된다. resolve('안녕') 이 호출되어 상태가 fulfilled 가 되고, 그 뒤에 아래 타이머가 상태를 출력한다. reject로 rejected 만들기 const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('hi'); reject('reject'); // 실패 상태로 바꾸고, 실패 이유로 'reject'를 전달 }, 3000); }); setTimeout(() => { console.log(promise); }, 3000); // hi // Promise {<rejected>: 'reject'} reject("reject") 는 단순히 값을 반환하는 게 아니라, reject 스위치를 작동시키면서 그 값을 실패 이유로 전달 하는 것이다. 이 코드를 콘솔에서 실행하면 Uncaught (in promise) reject 라는 빨간 오류도 함께 뜬다. 실패한 Promise를 catch 로 처리하지 않았기 때문이다. 바로 아래에서 catch 를 다룬다. 조건에 따라 성공과 실패 나누기 const promise = new Promise((resolve, reject) => { setTimeout(() => { const n = 100; if (typeof n === 'number') { resolve(n + 100); // 숫자이면 성공: 200 } else { reject('너 숫자 아닌데'); // 숫자가 아니면 실패 } }, 3000); }); setTimeout(() => { console.log(promise); // Promise {<fulfilled>: 200} console.log(typeof(promise)); // object }, 4000); Promise는 3초가 걸리는 작업 이고, 확인하는 타이머는 4초다. 3초에 resolve(200) 이 호출되어 이미 fulfilled 가 된 뒤에 출력하므로 성공으로 보인다. 만약 확인하는 타이머가 2초였다면 아직 작업이 끝나기 전이라 pending 으로 보였을 것이다. typeof(promise) 는 object 다. Promise도 객체이다. 2. then과 catch 상태를 직접 출력해 보는 방식은 불편하다. 보통은 성공했을 때 실행할 함수 와 실패했을 때 실행할 함수 를 등록한다. then: 성공했을 때 const promise = new Promise((resolve, reject) => { setTimeout(() => { const n = 100; if (typeof n === 'number') { resolve(n + 100); } else { reject('너 숫자 아닌데'); } }, 3000); }); promise.then((value) => { console.log(value); // 3초 뒤 200 }); then 은 "그 후에"라는 뜻이다. Promise가 성공했을 때 실행할 콜백을 등록하는 메소드다. executor에서 resolve 를 호출하면, then 에 전달한 콜백이 실행되면서 resolve 에 넘긴 값이 매개변수 value 로 들어온다. 실행 순서를 정리하면 이렇다. new Promise 가 실행되고 executor가 실행되어 setTimeout 이 예약된다. (3초 대기) promise.then(...) 이 등록되어 resolve 나 reject 를 기다린다. 3초 뒤 setTimeout 이 실행된다. n 이 숫자이므로 resolve(200) 이 호출된다. Promise가 pending 에서 fulfilled 로 바뀐다. 등록해 둔 then() 이 실행되면서 value 로 200을 받아 출력한다. 필기에서는 이 예제의 n 을 '100' (문자열)으로 적어 놓고 결과를 200으로 설명했는데, '100' 은 typeof 가 string 이라서 reject로 가고 then 은 실행되지 않는다. 위 코드는 숫자 100 으로 고쳤다. catch: 실패했을 때 const promise = new Promise((resolve, reject) => { setTimeout(() => { const n = null; if (typeof n === 'number') { resolve(n + 100); } else { reject('너 숫자 아닌데'); } }, 3000); }); promise.catch((error) => { console.log(error); // 3초 뒤 '너 숫자 아닌데' }); catch 는 then 과 반대로 Promise의 reject, 즉 실패값을 받아 실행되는 함수 를 등록한다. reject 에 전달한 값이 error 로 들어온다. null 의 typeof 는 'object' 라서 숫자 조건을 통과하지 못하고 reject로 간다. 3. 체이닝 then 을 이어 붙여서 앞 단계의 결과를 다음 단계로 넘길 수 있다. 각 then 이 return 한 값이 다음 then 의 value 가 된다. const myPromise = new Promise((resolve) => resolve('foo')); myPromise .then((value) => `${value} and bar`) .then((value) => `${value} and bar again`) .then((value) => `${value} and again`) .then((value) => `${value} and again`) .then((value) => { console.log(value); // foo and bar and bar again and again and again }) .catch((err) => { console.error(err); // 중간에 어디서든 실패하면 여기로 }); 위에서 아래로 읽으면 순서대로 일이 진행되는 것이 보인다. 중간 어느 단계에서 문제가 생기든 맨 끝의 catch 하나가 처리한다. Day.8의 콜백 함수로 같은 일을 하려면 콜백 안에 콜백을 계속 넣어야 해서 코드가 안쪽으로 파고드는데, 체이닝은 이를 평평하게 펴 준다. 4. async / await 필기 제목에 별표가 잔뜩 붙은 부분이다. "초 중요!" async 함수는 항상 Promise를 반환한다 async function test() { return { name: 'gildong', id: 'hong' }; } console.log(test()); // Promise {<fulfilled>: {name: 'gildong', id: 'hong'}} 함수 앞에 async 를 붙이면 그 함수의 반환값을 자동으로 Promise로 감싸 준다. 위 함수는 기다릴 코드가 없고 즉시 끝까지 실행되므로, 바로 fulfilled 상태의 Promise가 반환된다. 안에서 시간이 걸리는 경우 async function test() { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ name: 'gildong', id: 'hong' }); }, 2000); }); } console.log(test()); // Promise {<pending>} test().then(result => console.log(result)); // 2초 뒤 {name: 'gildong', id: 'hong'} console.log(test()) 는 Promise 객체 자체 를 출력한 것이다. 내부 Promise가 아직 기다리는 중이라 pending 으로 보인다. .then(result => ...) 의 result 는 resolve 에 넘긴 객체 값 이다. 2초 뒤 fulfilled 가 되면서 값이 나온다. await: 끝날 때까지 기다린다 async function test() { await new Promise((resolve) => setTimeout(resolve, 2000)); // 그냥 2초 동안 기다리는 장치다 return { name: 'aa', age: '123' }; } async function test2() { const data = await test(); // test()가 끝날 때까지 기다렸다가 결과를 data에 대입 console.log(data); } test2(); // 2초 뒤 {name: 'aa', age: '123'} await 는 Promise가 끝날 때까지(결과값을 받을 때까지) 기다린다. setTimeout(resolve, 2000) 은 "2초 뒤에 resolve 를 호출해라"라는 뜻이다. 값 없이 시간만 보내는 용도로 쓸 수 있다. async 가 필요한 경우는 두 가지다. 1 함수 안에서 await 를 쓸 때, 2 함수가 Promise를 반환하게 만들고 싶을 때. await 는 async 함수 안에서만 쓸 수 있다. 그래서 위의 test2 도 async 로 선언했다. function test() { return new Promise((resolve) => { setTimeout(() => { resolve('1초 후 완료'); }, 1000); }); } async function start() { console.log('시작'); const result = await test(); // Promise가 끝날 때까지 기다린 뒤, resolve로 전달된 문자열을 result에 저장 console.log(result); console.log('끝'); } start(); // 시작 // (1초 뒤) 1초 후 완료 // 끝 await 덕분에 비동기 코드인데도 위에서 아래로 순서대로 읽힌다. console.log('끝') 은 await 가 끝나기 전까지 실행되지 않는다. 5. try / catch로 실패 처리 체이닝에서는 성공이면 then , 실패이면 catch 였다. async/await에서는 성공이면 try 안, 실패(reject)이면 catch 안 으로 간다. console.log('홍길동'); function profile(id) { return new Promise((resolve, reject) => { setTimeout(() => { if (id == 1) { resolve({ id: 1, name: '김길동' }); } else { reject('프로필 없음'); } }, 1000); }); } console.log('이길동'); 여기까지의 출력은 홍길동 , 이길동 이다. profile() 을 호출하기 전이라 동기 코드만 실행된다. 체이닝 방식 id 를 100으로 주면 reject 가 호출되므로 catch 가 실행된다. profile(100) .then((user) => console.log(user)) .catch((error) => console.log(error)); // 홍길동 // 이길동 // (1초 뒤) 프로필 없음 profile(1) 이면 then 이 실행되어 {id: 1, name: '김길동'} 이 출력된다. async/await 방식 async function start() { try { // 예외가 날 수도 있는 코드 const pro = await profile(100); console.log(pro); } catch (error) { // 예외가 발생하면 여기서 처리 console.log(error); // 프로필 없음 } } start(); 방식 성공 실패 체이닝 .then() .catch() async/await try { } catch (error) { } await 한 Promise가 reject되면 그 지점에서 예외가 던져지고, catch 블록으로 넘어간다. 6. 콜백, Promise, async/await 비교 같은 일을 세 가지 방식으로 구현해 보면 차이가 보인다. 3초 뒤 'hi' 를 전달받아 출력하는 문제다. 1 콜백 function fun1(callback) { setTimeout(() => { callback('hi'); }, 3000); } fun1((msg) => { console.log(msg); }); 2 Promise function fun2() { return new Promise((resolve, reject) => { setTimeout(() => resolve('hi'), 3000); }); } fun2().then((value) => console.log(value)); 3 async/await async function start() { const msg = await fun2(); console.log(msg); } start(); 방식 결과를 받는 법 특징 콜백 함수를 인자로 넘기고, 완료 시점에 호출 단계가 많아지면 콜백 안에 콜백이 겹친다 Promise then , catch 로 연결 체이닝으로 평평하게 쓸 수 있다 async/await await 로 기다려서 변수에 받음 동기 코드처럼 위에서 아래로 읽힌다 커피 예제(Day.8의 coffee )도 같은 방식으로 바꿔 본다. // Promise를 반환하는 함수 function test() { return new Promise((resolve) => { setTimeout(() => { resolve('latte'); }, 2000); }); } async function coffee() { let coffee2 = await test(); console.log(coffee2); // 2초 뒤 latte } coffee(); 필기의 test 는 async function test() 로 되어 있었는데, 이미 new Promise 를 직접 반환하고 있으므로 async 를 붙이지 않아도 결과가 같다. 위에서는 뺐다. 수업 문제에서 필기에 "내가 푼 것"과 "강사님이 푼 것"을 나란히 적어 놓은 부분이 있다. // 내가 푼 것 const fun1 = new Promise((resolve, reject) => { setTimeout(() => resolve('hi'), 3000); }); fun1.then((value) => console.log(value)); // 강사님이 푼 것 function fun2() { return new Promise((resolve, reject) => { setTimeout(() => resolve('hi'), 3000); }); } 둘 다 hi 가 나오지만 차이가 있다. const fun1 = new Promise(...) 은 변수에 대입하는 순간 작업이 시작 되고, 호출할 때마다 새로 실행되는 것이 아니다. fun2() 처럼 함수로 감싸면 호출하는 시점에 Promise가 새로 만들어져 필요할 때 필요한 만큼 실행할 수 있다. async/await로 바꿀 때도 await fun2() 처럼 함수 호출 형태가 자연스럽다. 7. 이벤트 버블링 <style> #d1 { border: 3px solid black; padding: 20px; } #d1 a { text-decoration: none; border: 3px solid black; } </style> <div id="d1"> <a class="a1" href="#none">태그링크</a> </div> <script> let d1 = document.querySelector('#d1'); let a1 = document.querySelector('.a1'); // div를 클릭하면 테두리 색을 blue로 (고전 이벤트 방식) d1.onclick = function () { this.style.borderColor = 'blue'; }; // a1을 클릭하면 red a1.addEventListener('click', function (e) { e.stopPropagation(); // 전파 막기 this.style.borderColor = 'red'; }); </script> stopPropagation 줄을 지우고 a 를 클릭하면 a 의 테두리는 빨강, 부모 div 의 테두리도 파랑으로 바뀐다. 처음에는 a 만 클릭했는데 왜 부모까지 바뀌는지가 의문이었다. 답은 이벤트 버블링 이다. 자식 요소에서 이벤트가 발생하면 그 이벤트가 부모, 그 위 부모로 거품처럼 올라가며 전달된다. a 는 d1 의 자식이고 d1 에 클릭 이벤트가 걸려 있으므로, a 를 클릭하면 d1 의 onclick 도 함께 실행된다. e.stopPropagation() : 이벤트 전파를 막는다. a 를 클릭해도 부모 d1 의 이벤트가 실행되지 않는다. 반대로 위에서 아래로 내려가는 단계를 캡처링 이라고 한다. 이벤트는 캡처링 → 대상 → 버블링 순서로 흐르는데, 보통 쓰는 것은 버블링이다. Day.8의 To Do List에서 목록 전체( #list )에만 이벤트를 걸어도 안쪽 아이콘 클릭이 처리되었던 이유가 바로 이 버블링 덕분이다. 자식의 이벤트가 부모까지 올라오니까 부모에서 e.target 으로 받을 수 있었다. 8. || 와 && 로 값 고르기 (리액트 문법) || 와 && 는 Day.2에서 조건식에 쓰는 논리 연산자로 배웠다. 리액트에서는 이것을 조건에 따라 값을 고르는 용도 로 쓴다. falsy 값 아래 값들은 조건에서 모두 false로 인식 된다. false 0 "" (빈 문자열) null undefined NaN || 연산자 왼쪽이 true 로 판정되면 → 왼쪽 값 이 반환된다. 왼쪽이 false 로 판정되면 → 오른쪽 값 이 반환된다. const n = null; const m = n || '문자열'; console.log(m); // 문자열 (왼쪽이 falsy → 오른쪽) const n2 = 20; const m2 = n2 || '문자열'; console.log(
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
JavaScript_Day.9. JavaScript_Day.9 Day.8의 콜백 함수 다음 단계, Promise와 async/await. "비동기 작업이 끝난 뒤의 결과를 어떻게 받을까"에 대한 두 번째, 세 번째 답을 배운 날이다. 학습 아홉 번째 날이다. 오늘 배운 내용은 이렇다. Promise : 상태(pending, fulfilled, rejected), resolve 와 reject then , catch 와 체이닝 async/await 와 try/catch 콜백, Promise, async/await 비교 이벤트 버블링과 stopPropagation || , && 로 값 고르기 (리액트 문법) map , forEach , filter 1. Promise Promise는 비동기 작업(서버 요청,…
Open source