-
[Javascript] 콜백 함수와 PromiseJavascript 2024. 2. 6. 15:16
콜백 함수(Callback Function)
function repeatFunc(n, func) { for (let i = 0; i < n; i++) { func(i); } } function odd(i) { if (i % 2) console.log(i); } function even(i) { if (i % 2 == 0) console.log(i); } repeatFunc(5, odd) // 1, 3 repeatFunc(5, even) // 0, 2, 4
콜백함수 예시
- 매개변수를 통해 다른 함수의 내부로 전달되는 함수
- 매개변수를 통해 함수의 외부에서 전달받은 콜백 함수를 고차 함수라고 함
콜백 함수의 사용
1. 함수의 구조적 설계
- 콜백 함수를 사용하여 로직 처리 부분과 나머지 부분을 구분할 수 있으며, 이를 통해 함수의 역할을 나누고 재사용하는 등 구조적인 함수 설계가 가능해짐
function benchPress(day) { console.log('벤치프레스'); } function deadLift() { console.log('데드리프트'); } function squat() { console.log('스쿼트'); } function today(day) { console.log(`${day}의 운동 : `); } function todayWorkout(day) { if (day === '월요일') { today(day); benchPress(); } else if (day === '화요일') { today(day); deadLift(); } else if (day === '수요일') { today(day); squat(); } // 목요일... 금요일... 토요일... } todayWorkout('월요일'); todayWorkout('화요일');
목요일, 금요일, 토요일,.. 다른 요일의 루틴을 추가하려면 else if문을 추가해야함.
화요일의 루틴을 변경하고 싶으면 todayWorkout함수의 else if문에 있는 squat()함수를 직접 바꿔야함.
function benchPress(day) { console.log('벤치프레스'); } function deadLift() { console.log('데드리프트'); } function squat() { console.log('스쿼트'); } function today(day) { console.log(`${day}의 운동 : `); } function todayWorkout(day, routine) { today(day); routine(day); } todayWorkout('월요일', benchPress); todayWorkout('화요일', deadLift);
루틴을 추가하려면 todayWorkout함수에 요일과 루틴 함수를 입력하여 호출하면 됨.
화요일의 루틴을 변경하고 싶으면 todayWorkout의 두 번째 인자에 다른 함수를 입력.
2. 비동기 처리
- 자바스크립트는 코드를 위에서부터 순차적으로 실행하지만, 실제로 실행 시켰을 때 순서가 보장되지 않는 경우가 있음.
- 함수의 비동기 처리 결과에 대한 후속 처리를 수행하기 위한 용도로 콜백 함수가 사용된다.
- 대표적인 비동기 처리
- setTimeout(타이머 함수) : 두 번째 인자로 받은 만큼의 ms가 지난 이후 첫 번째 인자로 받은 콜백 함수를 실행
- fetch(API 호출 함수) : api를 호출의 결과를 콜백 함수로 받아 then, catch메서드로 후속 처리를 수행
// 1초 후에 콜백 함수가 실행됨 setTimeout(function () { console.log('Time is up!'); }, 1000); console.log('내가 setTimeout 함수 이후에 출력될까?'); console.log('아닌데ㅋ');
타이머 함수 예시
// fetch메서드를 이용한 API호출 fetch('https://url') .then(function (response) { // fetch 메서드가 성공하면 콜백 함수에 response인자를 받음 return response.json(); }) .then(function (data) { // 위의 then에서 리턴받은 json 메서드가 성공하면 콜백 함수로 data인자를 받음 console.log(data); }) .catch(function (error) { // fetch, then단계 어디서든 에러가 발생하면 catch구문에 도달하여 콜백 함수로 error인자를 받음 console.log(error); });
fetch 함수 예시
3. 이벤트 리스너
- 클릭과 같은 html 이벤트가 발생한 이후 콜백 함수를 실행
let button = document.getElementById("button"); button.addEventListener("click", function () { console.log("클릭"); // 콜백 함수에서 출력문을 실행 });
addEventListener 메서드 예시
4. 고차함수에서의 사용
- forEach(), map(), find() 와 같은 배열 고차함수에서, 배열의 각 요소에 대해 콜백 함수를 실행하고 결과를 반환
const nums = [1, 2, 3, 4]; let sum = 0; // forEach메서드 nums.forEach((num) => { sum += num; // 배열을 순회하며 콜백 함수를 수행 }); console.log(sum); // 10 // map메서드 const numMap = nums.map((num) => { return num + 2; // 배열을 순회하며, 최종적으로 각 요소마다 return문을 수행된 결과 배열을 반환 }); console.log(numMap); // (4) [3, 4, 5, 6]
forEach(), map()메서드 예시
Promise 객체의 등장 배경
- 비동기 함수는 비동기 처리의 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수도 없다. 결국 비동기 함수의 내부에서 후속 처리를 수행할 수 밖에 없다.
// 쇼핑몰의 판매탭에서 상품의 id를 가져오고 get('https://.../shopping/sales/1', (goodsId) => { // 상품의 id로 상품의 정보를 찾고 get(`https://.../shopping/goods/${goodsId}`, (goodsInfo) => { // 상품 정보에서 또 다른 무엇인가를 찾고.. get(`https://.../shopping/someWhere/${goodsInfo}`, (someId) => { // 다른 url로 이동해서 또.. get(`https://.../shopping/anyWhere/${someId}`, (something) => { console.log(something); }); }); }); });
콜백 헬 예시
- VanillaJS로 API 요청 코드를 작성하면 훨씬 복잡하지만, 간단하게 get('url주소', function(인자) {이후 수행될 콜백 로직})이라는 함수를 만들었다고 치자.
위의 예시와 같이 요청이 성공하면 응답을 가져오고, 그 응답을 이용해서 또 새로운 요청을 하고... 가 반복되다보면 들여쓰기가 반복되어 가독성이 떨어지며, 실수를 유발하는 원인이 된다. => 콜백 헬
fetch('https://url') .then(function (response) { // fetch 메서드가 성공하면 콜백 함수에 response인자를 받음 return response.json(); }) .then(function (data) { // 위의 then에서 리턴받은 json 메서드가 성공하면 콜백 함수로 data인자를 받음 console.log(data); }) .catch(function (error) { // fetch, then단계 어디서든 에러가 발생하면 catch구문에 도달하여 콜백 함수로 error인자를 받음 console.log(error); });
Promise Chaining을 이용한 코드
- 이를 해결하기 위해 ES6버전에서 Promise객체가 등장하였고, Promise의 후속 처리 메서드인 then, catch, finally를 이용하여 깔끔한 코드를 작성할 수 있다.
Promise 객체
1. Promise의 상태
Pending(대기)
const promise = new Promise(function (reslove, reject) {}); console.log(promise); // Promise {[[PromiseState]]: 'pending', [[PromiseResult]]: undefined, ...
- new Promise() 메서드로 호출할 때 콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject
- 처음 호출 시 대기 상태(Pending)가 되며, 결과 값은 undifined.
Fulfilled(이행)
const promise = new Promise(function (reslove, reject) { const data = 'resolved data'; reslove(data); }); promise .then(function (data) { console.log(data); // resolved data });
- 콜백 함수의 인자인 resolve함수를 실행하면 이행(Fulfilled) 상태가 되며, reslove함수의 인자가 결과 값으로 할당됨
- 이행 상태가 되면 then() 메서드를 이용하여 처리 결과를 콜백 함수로 받을 수 있다.
Rejected(실패)
const promise = new Promise(function (reslove, reject) { reject(new Error('요청 실패ㅠㅠ')); }); promise .then(function (data) { console.log(data); }) .catch(function (error) { console.log(error); // Error: 요청 실패ㅠㅠ ... });
- 콜백 함수의 인자인 rejected함수를 실행하면 실패(Rejected) 상태가 되며, reject함수의 인자가 결과 값으로 할당됨
- 실패 상태가 되면 catch() 메서드를 이용하여 처리 결과를 콜백 함수로 받을 수 있다.
2. Promise Chaining
콜백 헬 해결!
fetch( 'https://api.thecatapi.com/v1/images/search?size=med&mime_types=jpg&format=json&has_breeds=true&order=RANDOM&page=0&limit=1' ) .then(function (response) { return response.json(); }) .then(function (data) { console.log(data[0].url); }) .catch(function (error) { console.log(error); }) .finally(function () { console.log('난 성공하든 실패하든 일단 출력된단다.'); }); // 출력 결과 // https://cdn2.thecatapi.com/images/FUJOW3SIi.jpg // 난 성공하든 실패하든 일단 출력된단다.
- Promise객체의 등장 배경에서 살펴본 바와 같이 then(), catcth(), finally() 메서드를 이용하여 콜백 헬을 해결할 수 있다.
- 이처럼 연속적인 호출이 가능한 이유는 then(), catcth(), finally() 메서드가 모두 Promise 객체를 반환하기 때문이다.
3. Promise.all()
여러 개의 비동기 처리를 병렬적으로 처리하는 메서드
- Promise.all() 메서드는 인자를 Promise객체를 요소로 갖는 배열로 받아서 비동기 작업을 병렬적으로 처리한다.
- 인자로 전달받은 배열의 모든 Promise가 fulfilled상태가 되면 종료한다.
따라서 모든 처리에 걸리는 시간은 가장 늦게 fulfilled상태가 되는 작업보다 조금 길다. - 배열의 Promise중 하나라도 rejected 상태가 되면 나머지가 fulfilled상태가 되는 것을 기다리지 않고 즉시 종료한다.
Cat API를 활용한 예제
예제를 위해 https://thecatapi.com/의 API를 사용하였습니다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <h1>Cat Image</h1> <div id="imgBox"></div> </body> <script> function insertImg(url) { const imgBox = document.getElementById('imgBox'); const img = new Image(); img.src = url; img.width = 300; img.height = 200; imgBox.appendChild(img); } const getCat = function () { return fetch( 'https://api.thecatapi.com/v1/images/search?size=med&mime_types=jpg&format=json&has_breeds=true&order=RANDOM&page=0&limit=1' ) .then(function (response) { return response.json(); }) .then(function (data) { const imgSrc = data[0].url; insertImg(imgSrc); }); }; // Promise Chaing을 이용하여 비동기 요청이 순차적으로 수행됨 getCat() .then(function () { return getCat(); }) .then(function () { return getCat(); }); </script> </html>
- Promise Chaining을 이용하여 작성한 코드는 개발자 도구의 네트워크창을 통해 확인해보았을 때, 이미지를 받아오고 그리는 작업이 순차적으로 수행된 것을 볼 수 있었다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <h1>Cat Image</h1> <div id="imgBox"></div> </body> <script> function insertImg(url) { const imgBox = document.getElementById('imgBox'); const img = new Image(); img.src = url; img.width = 300; img.height = 200; imgBox.appendChild(img); } const getCat = function () { return fetch( 'https://api.thecatapi.com/v1/images/search?size=med&mime_types=jpg&format=json&has_breeds=true&order=RANDOM&page=0&limit=1' ) .then(function (response) { return response.json(); }) .then(function (data) { const imgSrc = data[0].url; console.log(imgSrc); return imgSrc; }); }; // Promise.all을 사용하여 비동기 요청이 병렬적으로 수행됨 Promise.all([getCat(), getCat(), getCat()]).then((dataList) => { dataList.forEach((imgSrc) => { insertImg(imgSrc); }); }); </script> </html>
- Promise.all() 메서드를 사용했을 때에는 이미지를 받아오고 그리는 작업이 병렬적으로 수행되었다.
'Javascript' 카테고리의 다른 글
[Javascript] this와 화살표 함수 (0) 2024.02.25 [Javascript] Javascript의 특징 (0) 2024.01.25