-
Notifications
You must be signed in to change notification settings - Fork 0
week06 yoochul1
YOOCHULKIM edited this page Mar 26, 2023
·
2 revisions
- Promise 등장배경
- 콜백함수 문제점
- 콜백 헬
- Error catch 불가
- 콜백함수 문제점
- 예상 햇던 것과 다르게 동작, (아래 예시 response 가 undefined)
- setTimeout의 콜백이 비동기로 동작하는것도 비슷
- get 함수의 onload 이벤트 핸들러는 비동기로 동작
// GET 요청을 위한 비동기 함수
const get = url => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
// 비동기로 실행
xhr.onload = () => {
if (xhr.status === 200) {
// ① 서버의 응답을 반환한다.
return JSON.parse(xhr.response);
}
console.error(`${xhr.status} ${xhr.statusText}`);
};
};
const response = get('https://jsonplaceholder.typicode.com/posts/1');
console.log(response); // undefined
// 예상값
/*
{
"userId": 1,
"id": 1,
"title": "sunt aut facere ...",
"body": "quia et suscipit ..."
}
*/let g = 0;
// 비동기 함수인 setTimeout 함수는 콜백 함수의 처리 결과를 외부로 반환하거나
// 상위 스코프의 변수에 할당하지 못한다.
setTimeout(() => { g = 100; }, 0);
console.log(g); // 0이를 해결하기 위해서는 onload 내부에서 콜백으로 후속 처리를 해야된다
그러나...
get('/step1', a => {
get(`/step2/${a}`, b => {
get(`/step3/${b}`, c => {
get(`/step4/${c}`, d => {
console.log(d);
});
});
});
});// GET 요청을 위한 비동기 함수
const get = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 서버의 응답을 콜백 함수에 전달하면서 호출하여 응답에 대한 후속 처리를 한다.
callback(JSON.parse(xhr.response));
} else {
console.error(`${xhr.status} ${xhr.statusText}`);
}
};
};
const url = 'https://jsonplaceholder.typicode.com';
// id가 1인 post의 userId를 취득
get(`${url}/posts/1`, ({ userId }) => {
console.log(userId); // 1
// post의 userId를 사용하여 user 정보를 취득
get(`${url}/users/${userId}`, userInfo => {
console.log(userInfo); // {id: 1, name: "Leanne Graham", username: "Bret",...}
});
});아래 예시는 에러를 캐치하지 못한다. 이유는, setTimeout은 비동기함수를 콜백함수가 호출되기를 기다리지 않고 바로 콜스택에서 제거되고, 콜백 함수만 남았기 때문이다. 쉽게 말해, catch는 setTimeout과 함께 간다. 따라서, setTimeout의 콜백함수가 던진 에러는 catch되지 않는다.
try {
setTimeout(() => { throw new Error('Error!'); }, 1000);
} catch (e) {
console.error('캐치한 에러', e);
}- ES6에 표준 빌트인 객체로 등장
- 콜백함수의 2개 인수
resolvereject
- 비동기 처리 status
-
pending/fulfilled/rejected
-
- 프로미스는 비동기 처리 상태와 결과를 관리하는 객체로 볼 수 있다.
- 상태가 변할때, 후속처리 메서드에 인수로 전달된 콜백 함수가 선택적으로 호출된다.
- 모든 후속처리 메서드는 promise를 반환
fulfilled된 프로미스


앞선 만든 get을 다시 구현한다면,
// GET 요청을 위한 비동기 함수
const promiseGet = url => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) { // 성공적으로 응답을 전달받으면
// resolve 함수 호출
resolve(JSON.parse(xhr.response));
} else {
// 에러 시 reject 함수 호출
reject(new Error(xhr.status));
}
};
});
};
const url = 'https://jsonplaceholder.typicode.com';
promiseGet(`${url}/posts/1`)
.then(res => console.log(res))
.catch(err => console.error(err))
.finally(() => console.log('Bye!'));프로미스 여러번 체이닝으로 사용시
// id가 1인 post의 userId를 취득
// then 은 promise 를 계속 반환 한다.
// 콜밸 헬 제거!!
promiseGet(`${url}/posts/1`)
.then(({ userId }) => promiseGet(`${url}/users/${userId}`))
.then(userInfo => console.log(userInfo))
.catch(err => console.error(err));다만, 프로미스도 콜백함수를 사용해야 될 수 있다. 콜백 패턴은 가독성이 좋지 않으므로, ES8 에서 나온 async / await 을 사용하자. 그러면, 프로미스의 후속처리 메서드 없이 동기 처럼 처리 결과를 반환하도록 구현할 수 있다.
const url = 'https://jsonplaceholder.typicode.com';
(async () => {
// id가 1인 post의 userId를 취득
const { userId } = await promiseGet(`${url}/posts/1`);
// 취득한 post의 userId로 user 정보를 취득
const userInfo = await promiseGet(`${url}/users/${userId}`);
console.log(userInfo);
})();- Promise.resolve
- Promise.reject
- Promise.all
- Promise.race
- Promise.allSettled
// 배열을 resolve하는 프로미스를 생성
const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log); // [1, 2, 3]
// ------------------ 생성자 함수 처리시 ------------------
const resolvedPromise = new Promise(resolve => resolve([1, 2, 3]));
resolvedPromise.then(console.log); // [1, 2, 3]// 에러 객체를 reject하는 프로미스를 생성
const rejectedPromise = Promise.reject(new Error('Error!'));
rejectedPromise.catch(console.log); // Error: Error!
// ------------------ 생성자 함수 처리시 ------------------
const rejectedPromise = new Promise((_, reject) => reject(new Error('Error!')));
rejectedPromise.catch(console.log); // Error: Error!- 병렬로 처리
- 모든 프로미스 상태가 fullfilled 될때 처리
const requestData1 = () => new Promise(resolve => setTimeout(() => resolve(1), 3000));
const requestData2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000));
const requestData3 = () => new Promise(resolve => setTimeout(() => resolve(3), 1000));
// 세 개의 비동기 처리를 순차적으로 처리
const res = [];
requestData1()
.then(data => {
res.push(data);
return requestData2();
})
.then(data => {
res.push(data);
return requestData3();
})
.then(data => {
res.push(data);
console.log(res); // [1, 2, 3] ⇒ 약 6초 소요
})
.catch(console.error);const requestData1 = () => new Promise(resolve => setTimeout(() => resolve(1), 3000));
const requestData2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000));
const requestData3 = () => new Promise(resolve => setTimeout(() => resolve(3), 1000));
Promise.all([requestData1(), requestData2(), requestData3()])
.then(console.log) // [ 1, 2, 3 ] ⇒ 약 3초 소요
.catch(console.error);Promise.all([
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 1')), 3000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 2')), 2000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 3')), 1000))
])
.then(console.log)
.catch(console.log); // Error: Error 3참고
프로미스가 아닌 경우 Promise.resolve 로 래핑
Promise.all([ 1, // => Promise.resolve(1) 2, // => Promise.resolve(2) 3 // => Promise.resolve(3) ]) .then(console.log) // [1, 2, 3] .catch(console.log);
깃헙 사용자 이름 취득 이거 왜 있음?? repo 가져오는걸로 못하나? 아님 위 json 사이트에서 가져와야할듯
// GET 요청을 위한 비동기 함수
const promiseGet = url => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 성공적으로 응답을 전달받으면 resolve 함수를 호출한다.
resolve(JSON.parse(xhr.response));
} else {
// 에러 처리를 위해 reject 함수를 호출한다.
reject(new Error(xhr.status));
}
};
});
};
const githubIds = ['jeresig', 'ahejlsberg', 'ungmo2'];
Promise.all(githubIds.map(id => promiseGet(`https://api.github.com/users/${id}`)))
// [Promise, Promise, Promise] => Promise [userInfo, userInfo, userInfo]
.then(users => users.map(user => user.name))
// [userInfo, userInfo, userInfo] => Promise ['John Resig', 'Anders Hejlsberg', 'Ungmo Lee']
.then(console.log)
.catch(console.error);- 가장 먼저 처리된 프로미스를 반환한다. (즉, 먼저 fulfilled 된다 )
- 제일 먼저 에러가 발생할 경우 rejected 된 프로미스를 먼저 반환 (catch 사용한다면~)
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
])
.then(console.log) // 3
.catch(console.log);Promise.race([
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 1')), 3000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 2')), 2000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 3')), 1000))
])
.then(console.log)
.catch(console.log); // Error: Error 3- ES11 에서 도입
- 모든 settled 된 즉, fulfilled 나 rejected 상태가 되면 다음 실행
Promise.allSettled([
new Promise(resolve => setTimeout(() => resolve(1), 2000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error!')), 1000))
]).then(console.log);
/*
[
{status: "fulfilled", value: 1},
{status: "rejected", reason: Error: Error! at <anonymous>:3:54}
]
*/- 프로미스의 후속 처리 메서드는 Task Queue 가 아니라 Microtask Queue 에 저장된다.
- 프로미스 이외의 비동기 함수의 콜백함수나 이벤트 핸들러는 Task Queue 저장
- 우선순위
- Microtask Queue > Task Queue
따라서 아래는 2 -> 3 -> 1 순으로 출력
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));- 클라이언트 사이드 Web API
-
XMLHttpRequest보다 간단하고 프로미스 지원
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => console.log(response));fetch('https://jsonplaceholder.typicode.com/todos/1')
// response는 HTTP 응답을 나타내는 Response 객체이다.
// json 메서드를 사용하여 Response 객체에서 HTTP 응답 몸체를 취득하여 역직렬화한다.
.then(response => response.json())
// json은 역직렬화된 HTTP 응답 몸체이다.
.then(json => console.log(json));
// {userId: 1, id: 1, title: "delectus aut autem", completed: false}아래는 catch 될 것처럼 보이지만, OK 가 출력된다.
fetch 는 404, 500 에러에도 reject 하지 않고, ok 상태를 false 로 설정한 응답 객체를 resolve 한다. (단, 네트워크 자애나 cors 에러의 경우 promise로 reject 한다.)
const wrongUrl = 'https://jsonplaceholder.typicode.com/XXX/1'; // 부적절한 URL이 지정되었기 때문에 404 Not Found 에러가 발생한다. fetch(wrongUrl) .then(() => console.log('ok')) .catch(() => console.log('error'));올바르게 고친다면,
const wrongUrl = 'https://jsonplaceholder.typicode.com/XXX/1'; // 부적절한 URL이 지정되었기 때문에 404 Not Found 에러가 발생한다. fetch(wrongUrl) // response는 HTTP 응답을 나타내는 Response 객체다. .then(response => { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(todo => console.log(todo)) .catch(err => console.error(err));
const request = {
get(url) {
return fetch(url);
},
post(url, payload) {
return fetch(url, {
method: 'POST',
headers: { 'content-Type': 'application/json' },
body: JSON.stringify(payload)
});
},
patch(url, payload) {
return fetch(url, {
method: 'PATCH',
headers: { 'content-Type': 'application/json' },
body: JSON.stringify(payload)
});
},
delete(url) {
return fetch(url, { method: 'DELETE' });
}
};get
request.get('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then(todos => console.log(todos))
.catch(err => console.error(err));
// {userId: 1, id: 1, title: "delectus aut autem", completed: false}post
request.post('https://jsonplaceholder.typicode.com/todos', {
userId: 1,
title: 'JavaScript',
completed: false
}).then(response => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then(todos => console.log(todos))
.catch(err => console.error(err));
// {userId: 1, title: "JavaScript", completed: false, id: 201}patch
request.patch('https://jsonplaceholder.typicode.com/todos/1', {
completed: true
}).then(response => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then(todos => console.log(todos))
.catch(err => console.error(err));
// {userId: 1, id: 1, title: "delectus aut autem", completed: true}delete
request.delete('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then(todos => console.log(todos))
.catch(err => console.error(err));
// {}