일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- SQL
- 코딩테스트
- 항해99
- 데이터베이스시스템
- 코딩테스트준비
- 엘리스sw트랙
- Cookie
- 유노코딩
- aws
- 코드잇
- CSS
- 꿀단집
- nestjs
- 개발자취업
- TiL
- HTML
- Git
- MySQL
- 중간이들
- 파이썬
- redis
- 방송대
- 99클럽
- 방송대컴퓨터과학과
- Python
- JavaScript
- 파이썬프로그래밍기초
- 프로그래머스
- presignedurl
- node.js
- Today
- Total
배꼽파지 않도록 잘 개발해요
[코드잇] 자바스크립트 웹 개발 기본기 ② - 비동기 실행과 Promise 객체 본문
fetch 함수와 비동기 실행
• 동기 실행 : 코드를 순차적으로 한 줄씩 실행하는 방식
• 비동기 실행 : 코드를 순차적으로 진행하지 않고, 특정 작업이 완료될 때까지 기다리지 않고 다음 작업을 시작하는 방식
console.log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => { console.log(result); });
console.log('End');
여기서 then 메소드는 콜백을 등록만 함.
then 메소드가 콜백을 등록하면 바로 console.log('End')가 실행됨.
서버에 response가 도착하게 되면 등록해두었던 콜백들이 순서대로 실행되면서 response의 내용이 마지막으로 출력됨.
비동기 실행 함수들
setTimeout 함수
· 특정 함수의 실행을 원하는 시간만큼 뒤로 미루기 위해 사용하는 함수
setTimeout(콜백, 시간)
console.log('a');
setTimeout(() => { console.log('b'); }, 2000);
console.log('c');
콜백의 실행을 2000ms = 2s 만큼 미룸.
setInterval 함수
· 특정 콜백을 일정한 시간 간격으로 실행하도록 등록하는 함수
setInterval(콜백, 시간)
console.log('a');
setInterval(() => { console.log('b'); }, 2000);
console.log('c');
a, c가 출력된 이후 2초 간격으로 b가 반복 출력됨.
addEventListener 메소드
· 특정 이벤트의 발생이라는 조건이 만족될 때마다 실행됨.
addEventListener(이벤트 이름, 콜백)
btn.addEventListener('click', function (e) { // 해당 이벤트 객체가 파라미터 e로 넘어옵니다.
console.log('Hello Codeit!');
});
// 또는 arrow function 형식
btn.addEventListener('click', (e) => {
console.log('Hello Codeit!');
});
promise
· 작업에 관한 '상태 정보'
· 콜백헬 문제를 해결하기 위해 ES6(=ES2015)에 추가됨.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => { console.log(result); });
· 작업의 결과가 promise 객체에 저장됨.
→ promise 함수를 보면 작업이 실패했는지 성공했는지 알 수 있음.
만약 promise 객체가 fulfilled 상태이면 그 결과가 response로 들어감. (작업 성공 결과)
rejected 상태이면 작업실패 이유에 관한 정보를 갖게 됨. (작업 실패 정보)
- pending : 진행중
- fulfilled : 성공 + 성공결과
- rejected : 실패
Promise Chaining
프로미스 객체들을 계속해서 이어나감.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => {
const users = JSON.parse(result);
return users[0];
})
.then((user) => {
console.log(user);
const { address } = user;
return address;
})
.then((address) => {
console.log(address);
const { geo } = address;
return geo;
})
.then((geo) => {
console.log(geo);
const { lat } = geo;
return lat;
})
.then((lat) => {
console.log(lat);
});
then메소드가 새로운 프로미스 객체를 리턴함.
여기 있는 then이 리턴하는 값들이 다 다르다는 뜻임.
promise 객체는 처음 pending 상태임.
나중에 then 메소드로 등록한 콜백을 실행함.
콜백에서 어떤 값 리턴하면 then 메소드가 리턴했던 promise 객체가 영향을 받음.
then메소드가 리턴한 promise객체는
- promise 객체를 리턴하는 경우 : 콜백이 리턴한 프로미스 객체와 동일한 상태를 갖게 됨.
- promise 객체가 아닌 값(숫자, 문자열, 일반 객체)을 리턴하는 경우 : : fulfuilled 상태로 작업 성공 결과를 가짐.
text 메소드와 json 메소드는 Promise 객체를 리턴하는 메소드임.
- text 메소드 : fulfilled 상태, response body의 내용을 string 타입으로 변환한 값을 '작업 성공 결과'로 가진 Promise 객체를 리턴함.
- json 메소드 : fulfilled 상태, response의 body에 있는 JSON 데이터를 자바스크립트 객체로 Deserialize 해서 생겨난 객체를 '작업 성공 결과'로 가진 Promise 객체를 리턴함.
→ 콜백에서 리턴한 Promise 객체로부터 새로운 Chain이 시작됨.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => {
const users = JSON.parse(result);
return users[0];
console.log(user);
const { address } = user;
console.log(address);
const { geo } = address;
console.log(geo);
const { lat } = geo;
console.log(lat);
});
response.text()처럼 프로미스 객체를 리턴하는 콜백의 경우를 제외하고는
하나의 콜백 안에 then 없이 몰아서 써도 됨.
프로미스 체이닝 용도
· 비동기 작업을 순차적으로 해야할 때 전체 코드를 좀 더 깔끔하게 나타내기 위해서 사용함.
· 두 코드는 비슷한 동작을 하지만, 두 번째 코드는 더 명시적으로 프로미스 체이닝을 사용하여 코드를 구성하고 있음.
→ 코드의 가독성과 유지보수성을 향상시킬 수 있음.
console.log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => {
const users = JSON.parse(result);
const { id } = users[0]; // 사용자 ID
fetch(`https://jsonplaceholder.typicode.com/posts?userId=${id}`)
.then((response) => response.text())
.then((posts) => {
console.log(posts);
});
});
console.log('End');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => {
const users = JSON.parse(result);
const { id } = users[0]; // 사용자 ID
return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${id}`);
})
.then((response) => response.text())
.then((posts) => {
console.log(posts);
});
console.log('End');
· then 메소드 안 콜백에서 프로미스 객체를 리턴하면?
→ then 메소드가 리턴했던 프로미스 객체도 fetch함수가 리턴하는 프로미스 객체와 똑같은 상태와 결과를 갖게 됨.
rejected 상태가 되면 실행할 콜백
만약 프로미스 객체가 fulfilled 상태가 아니라 rejected 상태가 됐을 때 실행하고 싶은 콜백이 있다면?
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text(), (error) => { console.log(error); })
.then((result) => { console.log(result); });
- 프로미스 객체가 fulfilled 상태일 때 실행 : (response) => response.text()
→ 파라미터로 작업 성공 결과가 넘어옴. - 프로미스 객체가 rejected 상태일 때 실행 : (error) => { console.log(error); }
→ 파라미터로 작업 실패 정보가 넘어옴.
then 메소드
fetch('https://www.google.com') // Promise-1 (A)
.then((response) => response.text()) // Promise-2 (B)
.then((result) => { console.log(result) }, (error) => { alert(error) });
A의 상태 | A의 리턴값 | |
콜백에서 Promise 객체를 리턴 | B(콜백이 리턴한 Promise 객체)와 같음 | B와 같음 |
콜백에서 Promise 객체가 아닌 일반적인 값을 리턴 |
fulfilled | 해당 리턴값을 작업 성공 결과로 가짐 |
콜백에서 아무것도 리턴하지 않음 | fulfilled | undefined를 작업 성공 결과로 가짐 |
콜백 실행 중 에러 발생 | rejected | 해당 에러 객체를 작업 실패 정보로 가짐 |
콜백이 실행되지 않음 | 이전 프로미스 객체와 동일한 상태 | 이전 프로미스 객체와 동일한 결과 |
실행된 콜백이 아무 값도 리턴하지 않는 경우
- then 메소드가 리턴했던 Promise 객체 : fulfilled 상태
- 작업 성공 결과 : undefined
실행된 콜백 내부에서 에러가 발생할 때
정의하지 않는 함수 콜백에서 사용
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => {
...
add(1, 2); // ReferenceError 발생
...
});
의도적으로 throw문을 사용해서 에러 발생
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => {
...
throw new Error('failed');
...
});
const promise = fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => { throw new Error('test'); });
promise 객체가 rejected 상태이고, 발생한 Error 객체를 그 작업 실패 정보로 갖고 있음
콜백이 실행되지 않는 경우
// Internet Disconnected
fetch('https://www.google.com') // Promise-1
.then((response) => response.text()) // Promise-2
.then((result) => { console.log(result) }, (error) => { alert(error) });
만약 인터넷이 끊어지는 경우
Promise-1 객체의 상태 : rejected
→ 그 바로 아래 then 메소드의 두 번째 콜백 실행되어야 하는데 없음.
→ then 메소드가 리턴한 Promise-2 객체는 이전 Promise 객체와 동일한 상태와 결과를 갖게 됨.
→ Promise-2에는 rejected 상태로, 작업 실패 정보가 있음.
catch 메소드
then 메소드의 rejected 상태가 되었을 때
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text(), (error) => { console.log(error); })
.catch((error) => { console.log(error); })
.then((result) => { console.log(result); });
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text(), (error) => { console.log(error); })
.then(undefined, (error) => { console.log(error); }) // 이거랑 같음
.then((result) => { console.log(result); });
위 세 가지가 다 같은 상태임.
(1)
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => { console.log(result) }, (error) => { console.log(error); });
(2)
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.catch((error) => { console.log(error); })
.then((result) => { console.log(result); });
(3)
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then(undefined, (error) => { console.log(error); })
.then((result) => { console.log(result); });
catch 함수는 마지막에 써야 함.
catch 함수 뒤에서 에러를 발생시키면 에러를 잡지 못하는 걸 알 수 있음.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.catch((error) => { console.log(error); })
.then((result) => {
console.log(result);
throw new Error('test');
});
then 메소드가 리턴했던 프로미스 객체는 rejected 상태가 됨.
rejected 상태의 프로미스만 남고, 딱히 어떤 처리를 하지 않으면 웹브라우저는 에러로 인식함.
작업 결과는 위에 response.text()가 그대로 반환됨.
catch 함수를 맨 뒤로 보내서 실행
throw new Error로 인위적으로 오류 만들기
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => {
console.log(result);
throw new Error('test');
})
.catch((error) => { console.log(error); });
잘못된 URL 입력하기
결론 :
· catch 메소드는 가장 마지막에 쓰자.
· 만약 fulfilled 상태이면 다음 .then 블록이 실행되고, rejected 상태이면 가장 가까운 .catch 블록으로 이동함.
fetch(어쩌구)
.then(어쩌구)
.catch((error) => { // -- C
// 미리 저장해둔 일반 뉴스를 보여주기
const storedGeneralNews = getStoredGeneralNews();
return storedGeneralNews;
})
.then((result) => { /* 화면에 표시 */ }) // -- D
.catch((error) => { /* 에러 로깅 */ }); // -- E
- C : rejected 상태 (에러가 발생하면 rejected 상태가 됨) → .catch 블록에서 return storedGeneralNews;가 실행되면 fulfilled 상태로 전이
- D : fulfilled 상태 (에러가 아니라 .catch 블록에서 반환된 값이 정상적으로 전달됨)
- E : rejected 상태 (에러가 발생하면 rejected 상태가 됨)
finally 메소드
· 프로미스 객체의 상태와 상관없이 항상 실행하고 싶은 콜백이 있을 때 사용
· finally 객체 안의 콜백은 그 전의 프로미스 객체가 fulfilled 상태가 되든 rejected 상태가 되든 상관없이 항상 실행되게 됨.
· finally 메소드 안의 콜백은 작업 성공 결과나 실패 정보가 필요하지 않아서 딱히 파라미터가 필요 없음.
· finally 메소드 용도 :
- 프로미스체이닝에서 작업을 수행하기 위해서 사용했던 자원을 정리
- 로그기록을 남기기
- 특정 변수의 값을 변경해주어야 할 때
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => { console.log(result); })
.finally(() => { console.log('exit'); });
catch 메소드가 리턴했던 프로미스 객체가 fulfilled 상태가 되고, undefined를 작업 성공 결과로 갖게 됨.
fetch('https://jsonplaceholder.typicode.commm/users')
.then((response) => response.text())
.then((result) => { console.log(result); })
.catch((error) => { console.log(error); })
.finally(() => { console.log('exit'); });
catch 메소드가 리턴한 프로미스 객체를 rejected 상태로 만들기 위해 catch 메소드 안에서 에러를 발생시킴.
fetch('https://jsonplaceholder.typicode.commm/users')
.then((response) => response.text())
.then((result) => { console.log(result); })
.catch((error) => {
console.log(error);
throw new Error('from catch method');
})
.finally(() => { console.log('exit'); });
catch 메소드에서 에러가 발생하더라도 finally 메소드 안의 콜백은 실행됨.
// 첫 번째 fetch 요청: 'https://www.error.www'에 대한 promise 객체를 반환하며, reject 상태로 전환됨
fetch('https://www.error.www')
.then((response) => response.text())
// 두 번째 콜백함수가 없기 때문에 아무런 콜백도 실행되지 않음.
// then 메소드가 리턴한 promise 객체는 reject 상태이며, 콜백함수가 리턴한 promise 객체도 reject 상태
.then((result) => { console.log(result); })
// 위와 같음.
// catch 메소드: 에러가 발생하면 'Hello'를 출력하고 'test'라는 에러를 던지면서 reject 상태로 전환
.catch((error) => { console.log('Hello'); throw new Error('test'); })
// catch 메소드가 리턴한 promise 객체는 fulfilled 상태였다가 에러로 'rejected'상태가 됨. 작업 성공 결과로 undefined를 반환
// ---------→ 다음 catch 메소드로 감.
// 다음 then 메소드: 콜백 함수 없음, 따라서 상태와 값은 이전 promise 객체와 동일
.then((result) => { console.log(result); })
// 다음 then 메소드: 콜백 함수가 없고, 두 번째 인자로 들어간 함수는 아무 동작도 하지 않아 undefined를 반환
// 이로 인해 현재 promise 객체는 fulfilled 상태가 되며, undefined 반환
.then(undefined, (error) => { })
// -----> 다음 then메소드로 감.
// catch 메소드: 에러가 발생하면 'JS'를 출력하고, 현재 promise 객체는 fulfilled 상태를 유지
.catch((error) => { console.log('JS'); })
// 다음 then 메소드: 콜백 함수 없고, 이전 promise 객체와 동일한 상태와 값을 가짐
.then((result) => { console.log(result); })
// ------→ fulfilled 상태로, undefined가 출력됨
// finally 메소드: 어떤 상황이든 실행되어 'final'을 출력
.finally(() => { console.log('final'); });
// ------→ final 출력
Promise 객체 생성
직접 프로미스 객체를 생성
const p = new Promise((resolve, reject) => {
});
자바스크립트에서 'new'는 새로운 객체 생성
'executor' 함수
(resolve, reject) => {}
const p = new Promise((resolve, reject) => {
setTimeout(() => { resolve('success');}, 2000);
});
p.then((result) => { console.log(result); };
resolve는 프로미스 객체를 fulfilled 상태로 만드는 함수임
2초후 p는 fulfilled 상태가, resolve의 아규먼트로 들어간 success라는 문자열은 작업성공결과가 됨.
const p = new Promise((resolve, reject) => {
setTimeout(() => { reject(new Error('fail'));}, 2000);
});
p.catch((error) => { console.log(error); };
reject 함수는 생성된 프로미스 객체를 rejected 상태로 만듦.
reject 함수의 아규먼트로 넣은 에러객체가 작업 실패 정보가 됨.
실무에서는 Promise 객체를 직접 만드는 일은 드물다.
Promisify
· 전통적인 형식의 비동기 실행 함수를 Promise 객체로 감싸서 그 프로미스 객체를 리턴하는 형식으로 만드는 작업
· 기존의 비동기 실행 함수(setTimeout, setInterval, addEventListener)의 콜백이 리턴하는 값을 Promise Chain에서 사용할 경우, 해당 함수를 감싸서 Promise 객체를 직접 생성하도록 해야 함.
수정 전
function wait(text, milliseconds) {
setTimeout(() => text, milliseconds); // 여기서 반환된 text는 무시됨
}
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => wait(`${result} by Codeit`, 2000)) // wait 함수는 값을 반환하지 않음
.then((result) => { console.log(result); });
위 코드에서 wait 함수는 setTimeout 내에서 어떤 값을 반환하고 있지 않음.
wait 함수가 반환한 값은 undefined이며, 이는 마지막 .then() 블록에서 사용됨.
따라서 console.log(result)에서 출력되는 값은 undefined임.
수정 후
function wait(text, milliseconds) {
return new Promise((resolve) => {
setTimeout(() => resolve(text), milliseconds);
});
}
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.text())
.then((result) => wait(`${result} end`, 2000))
.then((result) => { console.log(result); });
콜백이 여러 번 실행되어야하는 비동기 실행 함수인 경우에는 Promisify를 하면 안 됨.
pending 상태에 있던 Promise 객체(여기서는 p 객체)가 한번 fulfilled 상태 또는 rejected 상태가 되고 나면 Promise 객체의 상태 및 결과가 고정되어 그 뒤로는 바뀌지 않기 때문임.
- 직접 콜백을 사용하는 것이 더 적절할 수 있음.
- 각 콜백 실행에 대한 개별적인 Promise를 생성하고 그 결과를 적절히 처리하는 방법을 고려하는게 나음.
이미 상태가 결정된 Promise 객체
처음부터 바로 fulfilled 상태이거나 rejected 상태인 Promise 객체를 만드는 것
fulfilled 상태 Promise 객체 만들기
const p = Promise.resolve('success');
rejected 상태 Promise 객체 만들기
const p = Promise.reject(new Error('fail'));
Promise 객체 만드는 방법
· new 생성자와 executor 함수 사용
· resolve 메소드, reject 메소드
const p = Promise.resolve('success');
p.then((result) => { console.log(result); }, (error) => { console.log(error); });
const p = Promise.reject(new Error('fail'));
p.then((result) => { console.log(result); }, (error) => { console.log(error); });
여러 Promise 객체를 다루는 방법
all 메소드
여러 개의 프로미스를 배열로 받아 모든 프로미스가 이행될 때까지 기다린 후 하나의 프로미스로 반환하는 메소드
모든 프로미스가 이행되면 그 결과값들을 배열로 전달, 하나라도 거부되면 첫 번째로 거부된 프로미스의 이유로 전달됨.
// 각각 다른 시간이 걸리는 비동기 작업을 수행하는 함수들
function asyncTask1() {
return new Promise((resolve) => setTimeout(() => resolve('Task 1'), 2000));
}
function asyncTask2() {
return new Promise((resolve) => setTimeout(() => resolve('Task 2'), 1000));
}
function asyncTask3() {
return new Promise((resolve) => setTimeout(() => resolve('Task 3'), 1500));
}
// Promise.all을 사용하여 여러 비동기 작업을 동시에 처리
Promise.all([acyncTask1(), asyncTask2(), asyncTask3()])
.then((results) => {
console.log('All tasks completed:', results);
})
.catch((error) => {
console.error('One of the tasks failed:', error);
});
작업 성공 결과 : 각 개별 Promise 객체의 작업 성공 결과로 이루어진 배열
race 메소드
여러 개의 프로미스를 배열로 받아들이고, 그 중에서 가장 먼저 이행(resolve) 또는 거부(reject)된 프로미스의 상태와 결과를 가지는 새로운 프로미스를 반환
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.all() - JavaScript | MDN
The Promise.all() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises fulfill (including when an empty iterable is passed), with an array of the fulfillment valu
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
Promise.race() - JavaScript | MDN
The Promise.race() static method takes an iterable of promises as input and returns a single Promise. This returned promise settles with the eventual state of the first promise that settles.
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
Promise.allSettled() - JavaScript | MDN
The Promise.allSettled() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises settle (including when an empty iterable is passed), with an array of objects that d
developer.mozilla.org
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any
Promise.any() - JavaScript | MDN
The Promise.any() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when any of the input's promises fulfills, with this first fulfillment value. It rejects when all of the input's promises re
developer.mozilla.org
axios
axios 라고 하는 외부 패키지를 사용하여 Ajax 통신을 할 수 있음.
axios에서 제공하는 추가 기능이 필요한 경우에는 axios를 쓰고, 그런 기능이 필요하지 않고 별도의 패키지 다운로드를 원하지 않는 경우에는 fetch 함수를 사용
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
'코드잇 Codeit > Front-End' 카테고리의 다른 글
[코드잇] 자바스크립트 웹 개발 기본기 ③ - async/await을 활용한 세련된 비동기 코드 (0) | 2024.01.05 |
---|---|
[코드잇] 자바스크립트 객체 지향 기본기 (0) | 2023.12.31 |
[코드잇] 자바스크립트 웹 개발 기본기 ① - 웹 기초 다지기, Web API 배우기 (0) | 2023.12.26 |
[코드잇] 인터랙티브 자바스크립트 ② - 이벤트 살펴보기, 다양한 이벤트 (0) | 2023.12.25 |
[코드잇] 인터랙티브 자바스크립트 ① 시작하기, 브라우저와 자바스크립트 (1) | 2023.12.23 |