프라미스 체이닝
: 스크립트를 불러오는 것과 같이 순차적으로 처리해야하는 비동기 작업이 여러 개 있을 때 사용하는
비동기 처리 방법
: 프라미스 하나에 .then을 여러 개 추가하는 것은 체이닝이 아님
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
프라미스 반환하기
: .then(handler)에 사용된 핸들러가 프라미스를 생성하거나 반환하는 경우
이어지는 핸들러는 프라미스가 처리될 때까지 기다리다가
처리가 완료되면 그 결과를 받음
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
프라미스와 에러 핸들링
: 프라미스가 거부되면 제어흐름이 가장 가까운 rejection 핸들러로 넘어가기 때문에
프라미스 체인을 사용하면 에러를 쉽게 처리할 수 있음
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
암시적 try catch
: 프라미스 executor와 프라미스 핸들러 코드 주위에는 암시적인 try catch가 존재
: 예외가 발생하면 암시적 try catch에서 예외를 잡고 이를 reject 처럼 다룸
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
}).catch(alert); // Error: 에러 발생!
// 똑같이 동작함
new Promise((resolve, reject) => {
reject(new Error("에러 발생!"));
}).catch(alert); // Error: 에러 발생
: executor 주위의 암시적 try catch는 스스로 에러를 잡고 에러를 거부 상태의 프라미스로 변경 시킴
: .then 핸들러를 원하는 만큼 사용하다 마지막에 .catch 하나만 붙이면 모든 에러 처리 가능
다시 던지기
// 실행 순서: catch -> catch
new Promise((resolve, reject) => {
throw new Error("에러 발생!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// 에러 처리
} else {
alert("처리할 수 없는 에러");
throw error; // 에러 다시 던지기
}
}).then(function() {
/* 여기는 실행되지 않습니다. */
}).catch(error => { // (**)
alert(`알 수 없는 에러가 발생함: ${error}`);
// 반환값이 없음 => 실행이 계속됨
});
프라미스 API
: Promise 클래스에는 5가지 정적 메서드가 있음
Promise.all
: 복수의 프라미스를 동시에 실행시키고 모든 프라미스가 준비될 때 까지 기다림
: 이때 프라미스중 하나라도 reject 되면 에러와 함께 reject를 반환
: 에러가 발생되면 다른 프라미스는 무시됨
let promise = Promise.all([...promises...]);
Promise.all([
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(alert); // 프라미스 전체가 처리되면 1, 2, 3이 반환됩니다. 각 프라미스는 배열을 구성하는 요소가 됩니다.
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// 모든 응답이 성공적으로 이행되었습니다.
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // 모든 url의 응답코드가 200입니다.
}
return responses;
})
// 응답 메시지가 담긴 배열을 response.json()로 매핑해, 내용을 읽습니다.
.then(responses => Promise.all(responses.map(r => r.json())))
// JSON 형태의 응답 메시지는 파싱 되어 배열 'users'에 저장됩니다.
.then(users => users.forEach(user => alert(user.name)));
※ 이터러블 객체가 아닌 일반 값을 Promise.all(iterable)에 사용한 예시
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
Promise.allSettled
: 모든 프라미스가 처리될때까지 기다리고 모든 프라미스의 결과를 배열로 반환
- 응답이 성공할 경우
: {status:"fulfilled", value:result}
- 에러가 발생한 경우
: {status:"rejected", reason:error}
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
// 결과
[
{status: 'fulfilled', value: ...응답...},
{status: 'fulfilled', value: ...응답...},
{status: 'rejected', reason: ...에러 객체...}
]
※ Promise.allSettled의 폴리필
if(!Promise.allSettled) {
Promise.allSettled = function(promises) {
return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
status: 'fulfilled',
value
}), reason => ({
status: 'rejected',
reason
}))));
};
}
Promise.race
: Promise.all과 비슷하나, 가장 먼저 처리되는 프라미스의 결과를 반환
let promise = Promise.race(iterable);
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
Promise.resolve
: 결과값이 value인 이행 상태 프라미스를 생성
: 아래 코드와 동일한 작업 수행
let promise = new Promise(resolve => resolve(value));
: 호환성을 위해 함수가 프라미스를 반환하도록 해야할 때 사용
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
Promise.reject
: 결과 값이 error인 reject 상태의 프라미스 생성
: 아래 코드와 동일한 작업 수행
let promise = new Promise((resolve, reject) => reject(error));
프라미스화
: 콜백을 받는 함수를 프라미스를 반환하는 함수로 바꾸는 것
- 프라미스화 전
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생함`));
document.head.append(script);
}
// usage:
// loadScript('path/script.js', (err, script) => {...})
- 프라미스화 후
let loadScriptPromise = function(src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) reject(err)
else resolve(script);
});
})
}
// 사용법:
// loadScriptPromise('path/script.js').then(...)
마이크로태스크 큐
: 비동기 작업을 처리하기 위한 큐 (V8 엔진에서 사용하는 내부 큐)
: FIFO 방식의 작업 처리
: 실행할 것이 아무것도 남지 않을 때만 마이크로태스크 큐에 있는 작업이 실행됨
: 프라미스 작업이 가장 마지막에 수행 되는 이유임
비동기 작업을 먼저 처리하는 방법
: .then을 사용해 큐에 넣으면 됨
Promise.resolve()
.then(() => alert("프라미스 성공!"))
.then(() => alert("코드 종료"));
처리되지 못한 거부 (unhandlerejcetion)
: 마이크로태스크 큐 끝에서 프라미스 에러가 처리되지 못할 때 발생
: 프라미스 체인에 .catch를 추가해 에러를 처리하면 됨
: unhandlerejcetion은 마이크로태스트 큐의 모든 작업이 완료되었을 때 생성 됨
Refference
'Programming > JavaScript' 카테고리의 다른 글
모던 자바스크립트 10. 제너레이터 (0) | 2022.04.23 |
---|---|
모던 자바스크립트 (promise와 async, await) 9-3. async와 await (0) | 2022.04.23 |
모던 자바스크립트 (promise와 async, await) 9-1. 콜백 / 프라미스 (0) | 2022.04.23 |
모던 자바스크립트 (에러핸들링) 8-2. 커스텀 에러와 에러 확장 (0) | 2022.04.23 |
모던 자바스크립트 (에러핸들링) 8-1. try catch (0) | 2022.04.23 |