<자바스크립트 웹 개발 기본기>
3. 비동기 실행과 Promise 객체
fetch 함수와 비동기 실행
비동기 실행
한번 시작된 작업이 완료되기 전에 바로 다음코드로 실행이 넘어가고 나중에 콜백이 실행됨으로써 작업이 마무리 된다
console.log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>response.text())
.then((result)=>{console.log(result);});
console.log('End');
fetch 함수에 비동기 실행이 되는 부분이 있다
fecth 함수는 리퀘스트를 보내고 어떤 객체를 리턴한다
그 객체의 then 메소드로 콜백을 등록한다
→ 콜백은 서버로부터 리스폰스를 받았을 때 실행된다
💡fetch함수가 리턴하는 객체가 비동기 실행과 관련있음 → promise 객체
then은 콜백을 등록만 함 (바로 실행 x)
서버의 리스폰스가 도착하면 실행된다!
동기 실행과 비동기 실행
이미지 상단은 fetch 함수가 동기 실행된다고 가정했을 때의 경우
이미지 하단은 fetch 함수가 비동기 실행되는 실제 모습
알아야 하는 비동기 실행 함수들
1. setTimeout 함수
setTimeout 함수는, 특정 함수의 실행을 원하는 시간만큼 뒤로 미루기 위해 사용하는 함수
2. setInterval 함수
setInterval 함수는 특정 콜백을 일정한 시간 간격으로 실행하도록 등록하는 함수
3. addEventListener 메소드
addEventListener 메소드는 DOM 객체의 메소드
fetch 함수는 Promise 객체를 리턴
fetch 함수는 promise 객체를 리턴한다
promise 객체
• 어떤 작업에 대한 상태 정보를 갖고 있는 객체
• promise 객체를 보면 작업이 성공했는지 실패했는지를 알 수 있다
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>response.text())
.then((result)=>{console.log(result);});
💡promise 객체 상태
pending 진행중
fulfiled 성공
rejected 실패
• pending 에서 fulfiled 하면 promise 객체는 성공 결과도 함께 가진다
서버가 보내 준 리스폰스가 작업의 성공 결과에 해당한다 (작업 성공 결과)
• pending 에서 rejected가 되면 promise 객체는 작업 실패의 이유에 관한 정보를 갖게된다
fetch 함수를 사용한 코드, 다시 해석하기
console.log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>response.text()) // fulfiled 일때 실행할 콜백 등록
.then((result)=>{console.log(result);});
console.log('End');
//작업성공결과는 서버가 보내준 리스폰스
promise 객체의 작업 성공 결과는 첫 번째 콜백의 파라미터로 넘어온다
Promise Chaining
promise 객체에 then 메소드 연속적으로 붙이는 것을 말한다
console.log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>response.text())
.then((result)=>{
const users= JSON.parse(result);
return users[0];
})
.then((users)=>{
console.log(user);
const { address }=user;
return address;
}) //user 객체에서 address 프로퍼티 추출
.then((address)=>{
console.log(address);
const { geo }=address;
return geo;
})//address 객체에서 geo라는 프로퍼티 추출
.then((geo)=>{
console.log(geo);
const { lat }= geo ;
return lat;
})//geo 객체에서 lat 프로퍼티 추출
.then((lat)=>{
console.log(lat);
});
console.log('End');
then 메소드는 새로운 promise 객체를 리턴한다
then은 각각 별개의 promise 객체를 리턴함! → 체이닝이 가능한 이유
1.promise 객체를 리턴하는 경우
• then 메소드가 리턴했던 promise 객체는 콜백이 리턴한 프로미스 객체와 동일한 상태와 결과를 갖게 된다
2.promise 객체가 아닌 값을 리턴하는 경우
• then 메소드가 리턴했던 promise 객체는 fulfiled 상태가 되고 콜백의 리턴값을 작업 성공 결과로 갖게 된다
text, json 메소드도 Promise 객체를 리턴
1. text 메소드
fetch 함수로 리스폰스를 잘 받으면, response 객체의 text 메소드는, fulfilled 상태이면서 리스폰스의 바디에 있는 내용을 string 타입으로 변환한 값을 '작업 성공 결과'로 가진 Promise 객체를 리턴
이때 그 작업 성공 결과는 string 타입 (parse 메소드로 Deserialize를 해줘야한다)
2. json 메소드
fetch 함수로 리스폰스를 잘 받으면, response 객체의 json 메소드는, fulfilled 상태이면서, 리스폰스의 바디에 있는 JSON 데이터를 자바스크립트 객체로 Deserialize해서 생겨난 객체를 '작업 성공 결과'로 가진 Promise 객체를 리턴
💡response 객체의 text 메소드와 json 메소드가 사실 Promise 객체를 리턴하는 메소드
Promise Chaining이 필요한 경우
콜백처럼 프로미스 객체를 리턴하는 경우를 제외하곤 하나의 콜백 안에 몰아서 써도 된다
console. log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
. then ((response) => response. text() )
. then ((result) => {
const users = JSON.parse(result);
const user = users [0];
console. log(user);
const { address } = user;
console. log(address);
const { geo } = address;
console. log(geo);
const { lat } = geo;
console. log(lat);
});
console.log('End');
Promise Chaining
프로그램 비동기화를 할 때 전체 코드를 좀 더 깔끔하게 나타내기 위해서 사용
콜백 안에서 프로미스 객체를 리턴하는 경우에 콜백안에 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];
fetch('https://jsonplaceholder.typicode.com/posts?userId=${id}')
. then ((response) => response. text() )
. then ( (posts) => {
console.log (posts);
});
console.log('End' );
...................................................................
console. log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
. then ((response) => response. text())
. then ((result) => {
const users = JSON.parse(result);
const { id } = users [0];
return fetch('https://jsonplaceholder.typicode.com/posts?userId=${id}');
})
. then ((response) => response. text())
. then ( (posts) => {
console. log(posts);
});
console. log('End');
네모친 then 메소드에선 두 번째 함수가 리턴하는 promise 객체를 리턴하고 있다
💡then 메소드 안에 콜백에서 promise 객체를 리턴하면? →
then 메소드가 return 했던 promise 객체도 콜백에서 return한, fetch 함수가 리턴하는 프로미스 객체와 똑같은 상태와 결과를 갖게 된다
두 번째 fecth 함수가 리턴한 promise 객체의 then이 연결된 것이다
then 메소드가 return 했던 promise객체가 콜백이 리턴한 프로미스 객체와 동일한 상태와 결과를 가진다는 규칙 덕분에 가능
비동기 작업을 순차적으로 처리하기 위해서 promise chaining 사용
rejected 상태가 되면 실행할 콜백
promise 상태가 rejected 상태가 됐을 때 실행하고 싶은 콜백이 있다면?
→then 메소드의 두번째 파라미터로 넣는다
console.log('Start!');
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>response.text(),(error)=>{ console.log(error);})
.then((result)=>{console.log(result);});
인터넷을 끊고 코드를 실행하면 에러가 발생한다
→ fetch 함수가 리턴했던 promise 객체는 rejected 상태가 된다
TypeError라고 하는 에러 객체가 fetch함수가 리턴했던 promise 객체의 작업 실패 정보로 설정되었다는 것을 알 수 있다
즉 콜백에는 작업실패정보가 파라미터로 넘어온다!
then 메소드
const successCallback = function () { };
const errorCallback = function () { };
fetch('https://jsonplaceholder.typicode.com/users') // Promise-A
.then(successCallback, errorCallback); // Promise-B
(1) fetch 메소드가 리턴하는 Promise 객체를 Promise-A 객체라고 하고,
(2) then 메소드가 리턴하는 Promise 객체를 Promise-B 객체라고 한다
- fetch 함수의 작업이 성공해서 Promise-A 객체가 fulfilled 상태가 된 경우 : then 메소드 안의 "첫 번째" 콜백인 successCallback이 실행된다
- fetch 함수의 작업이 실패해서 Promise-A 객체가 rejected 상태가 된 경우 : then 메소드 안의 "두 번째" 콜백인 errorCallback이 실행된다
promise-B는, 실행된 successCallback 또는 errorCallback에서 무엇을 리턴하느냐에 따라
- 그 상태(fulfilled or rejected)와
- 결과(작업 성공 결과 or 작업 실패 정보)가 결정된다
1. 실행된 콜백이 어떤 값을 리턴하는 경우
1) Promise 객체를 리턴하는 경우
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((result) => { console.log(result) }
이렇게 콜백에서 Promise 객체를 리턴하는 경우에는 그 콜백을 등록한 then 메소드가 리턴했던 Promise 객체가 콜백이 리턴한 Promise 객체의 상태와 결과를 똑같이 따라 갖게 된다
2) Promise 객체 이외의 값을 리턴하는 경우
// Internet Disconnected
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json(), (error) => 'Try again!')
.then((result) => { console.log(result) });
두번째 콜백인 (error) ⇒ 'Try again! 이 실행된다
2. 실행된 콜백이 아무 값도 리턴하지 않는 경우
// Internet Disconnected
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json(), (error) => { alert('Try again!'); })
.then((result) => { console.log(result) });
콜백이 무언가를 리턴하는 게 아니라 이 코드에서처럼 단순히 alert 함수만 실행
→결과적으로 이 콜백은 아무런 값도 리턴하지 않은 것과 같다
함수가 아무것도 리턴하지 않으면 undefined를 리턴한 것으로 간주
3. 실행된 콜백 내부에서 에러가 발생했을 때
콜백 실행 중에 에러가 발생하면, then 메소드가 리턴한 Promise 객체는 rejected 상태가 되고, 그 작업 실패 정보로 해당 Error 객체를 갖게 된다
4. 아무런 콜백도 실행되지 않을 때
// Internet Disconnected
fetch('https://www.google.com') // Promise-1
.then((response) => response.text()) // Promise-2
.then((result) => { console.log(result) }, (error) => { alert(error) });
이런 경우에 then 메소드가 리턴했던 Promise-2 객체는, 이전 Promise 객체와 동일한 상태와 결과를 갖게 된다
->Promise-2 객체는 Promise-1 객체처럼 rejected 상태가 되고, 똑같은 작업 실패 정보를 갖게 된다
catch 메소드
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>response.text())
.catch((error)=>{ console.log(error);}) // .then(undefined, (error)=>{ console.log(error);})
.then((result)=>{console.log(result);});
catch 메소드
→then 메소드를 변형시킨 것
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');
});
rejected 객체가 마지막에 리턴되고 딱히 어떤 처리를 해주지 않으면 웹 브라우저는 에러로 인식한다
→ catch메소드를 아래로 내려야하는 이유
만약 중간에 에러가 발생해도 catch 메소드가 그 대안을 뒤로 넘겨줄 수 있으면 catch 메소드를 중간에 써도 된다
finally 메소드
promise 객체가 어떤 상태이던지 상관없이 항상 실행하고 싶은 콜백이 있을 때
→ finally 사용
fetch('https://jsonplaceholder.typicode.com/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');});
finally 메소드
• catch 메소드보다도 더 뒤에 쓴다
• 항상 실행해야할 코드를 finally에 넣는다
로딩 아이콘 코드 수정해보기
코드 상단의 URL 두 개 중에서 첫 번째는 JSON 데이터가 오는 URL이고, 두 번째는 HTML 등의 코드가 오는 URL
let isLoading = true;
/* ..다른 코드들 */
// const url = 'https://jsonplaceholder.typicode.com/users';
const url = 'https://www.google.com';
fetch(url)
.then((response) => {
const contentType = response.headers.get('content-type');
if (contentType.includes('application/json')) {
return response.json();
}
throw new Error('response is not json data');
})
.then((result) => {
// 리스폰스 처리
console.log(result);
})
.catch((error) => {
// 에러 처리
console.log(error);
})
.finally(() => {
isLoading = false;
console.log(isLoading);
});
/* ..다른 코드들 */
catch 메소드와 finally 메소드 퀴즈
fetch('https://www.error.www') //1 리턴값 rejected
.then((response) => response.text()) //2 x -> 상태가 rejected이기 때문에
.then((result) => { console.log(result); }) //3 x
.catch((error) => { console.log('Hello'); throw new Error('test'); }) //4 리턴값 rejected
.then((result) => { console.log(result); }) //5
.then(undefined, (error) => { }) //6 catch 메소드랑 동일 리턴값 fullfilled, undefined
.catch((error) => { console.log('JS'); }) //7 x -> 상태가 fulfilled이기 때문에
.then((result) => { console.log(result); }) //8 출력 undefined
.finally(() => { console.log('final'); }); //9 출력 final
- 우선 처음부터 존재하지 않는 url을 fetch했기 때문에 1번 라인에서 리턴되는 Promise객체는 rejected상태가 된다
- 2번, 3번 라인에서는 rejected 상태인 결과값을 처리해줄 콜백함수가 없기 때문에 그대로 4번 라인까지 내려온다.
- 여기서 catch 메소드를 만나 "Hello"를 출력하고, 에러를 고의로 발생시켰기 때문에 또 다시 rejected 상태가 된 후 에러 객체를 리턴한다. 그리고 전과 같은 이유로 이 rejected 상태를 처리해주기 위해 6번 라인까지 내려오게 된다.
- 첫번째 인자로 undefined를 가지는 then 메소드는 catch 메소드와 같으니, 두번째 인자에 있는 (error) => {} 함수가 실행됩니다. 그러나 아무것도 출력되는게 없고, 역시 아무것도 리턴되지 않습니다. 따라서 자동으로 fulfilled 상태에, undefined를 리턴값으로 가지게 된다.
- fulfilled 상태이므로, 7번 라인은 패스하고 8번 라인에서 result가 undefined를 받아 그대로 undefined가 출력된다.
- 마지막으로 finally 메소드에 있는 final이 출력된다.
💡 출력값 Hello, undefined, final
직접 만들어보는 Promise 객체
promise 객체를 생성하는 코드
resolve 파라미터 : 생성될 promise 객체를 fulfilled 상태로 만들 수 있는 함수가 연결
reject 파라미터 : 생성될 promise 객체를 rejected 상태로 만들 수 있는 함수가 연결
Promisify
전통적인 형식의 비동기 실행 함수를 사용하는 코드를, Promise 기반의 코드로 변환하기 위해 Promise 객체를 직접 만드는 경우
전통적인 형식의 비동기 실행 함수를 Promise 객체로 감싸서 그 Promise 객체를 리턴하는 형식으로 만드는 작업을 Promisify(프로미스화하다)라고 한다
이미 상태가 결정된 Promise 객체
1. 이미 상태가 결정된 Promise 객체 만들기
1) fulfilled 상태의 Promise 객체 만들기
const p = Promise.resolve('success');
2) rejected 상태의 Promise 객체 만들기
const p = Promise.reject(new Error('fail'));
2. Promise 객체의 작업 성공 결과 또는 작업 실패 정보
Promise 객체의 상태가 fulfilled 또는 rejected 상태이기만 하면, 어느 시점이든, 몇 번이든 then 메소드를 붙여서 해당 결과를 가져올 수 있다
const p = new Promise((resolve, reject) => {
setTimeout(() => { resolve('success'); }, 2000); // 2초 후에 fulfilled 상태가 됨
});
p.then((result) => { console.log(result); }); // Promise 객체가 pending 상태일 때 콜백 등록
setTimeout(() => { p.then((result) => { console.log(result); }); }, 5000); // Promise 객체가 fulfilled 상태가 되고 나서 콜백 등록
Promise 객체는 항상 결과를 줄 수 있는 공급자(Provider)이고 그것의 then 메소드는 그 결과를 소비하는 콜백인 소비자(Consumer)를 설정하는 메소드라는 사실
axios
fetch 함수→ Ajax 통신을 하는 함수 (비동기적 웹애플리케이션)
fetch 함수 말고도 Ajax 통신을 할 수 있는 방법이 존재 ⇒ axois 라고 하는 외부 패키지를 사용하는 것
axios
.get('<https://jsonplaceholder.typicode.com/users>')
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
});
axios 객체에는 fetch 함수에는 없는 다음과 같은 몇 가지 기능 및 장점
- 모든 리퀘스트, 리스폰스에 대한 공통 설정 및 공통된 전처리 함수 삽입 가능
- serialization, deserialization을 자동으로 수행
- 특정 리퀘스트에 대해 얼마나 오랫동안 리스폰스가 오지 않으면 리퀘스트를 취소할지 설정 가능(request timeout)
- 업로드 시 진행 상태 정보를 얻을 수 있음
- 리퀘스트 취소 기능 지원
'🏃♀️ 대외활동 > Codeit Boost-Node.js' 카테고리의 다른 글
6주차 스터디 자바스크립트 백엔드 개발 (1) [코드잇 부스트 백엔드 스터디] (0) | 2024.06.28 |
---|---|
5주차 스터디 자바스크립트 웹 개발 기본기 (4) async/await을 활용한 세련된 비동기 코드 [코드잇 부스트 백엔드 스터디 ] (2) | 2024.06.04 |
5주차 스터디 자바스크립트 웹 개발 기본기 (2) Web API [코드잇 부스트 백엔드 스터디 ] (1) | 2024.06.04 |
5주차 스터디 자바스크립트 웹 개발 기본기 (1) 웹 기초 [코드잇 부스트 백엔드 스터디 ] (2) | 2024.06.04 |
5주차 스터디 자바스크립트 객체 지향 기본기 (2) [코드잇 부스트 백엔드 스터디 ] (1) | 2024.06.04 |