Udemy_JavaScript: The Advanced JavaScript Concepts (2021) 강의를 바탕으로 메모한 내용입니다.
promise is an object that may produce a single value sometime in the future either resolved or rejected with a reason.
프로미스 객체는 미래의 성공 또는 실패일 때 일어날 결과를 리턴하겠다는 약속이다.
또한 자바스크립트에 내장된 객체이기 때문에 .then
, .catch
등의 메서드를 사용할 수 있다.
const promise = new Promise(function () {...})
프로미스 생성자 함수는 함수를 인자로 받는다. 이 함수의 매개변수가 resolve, reject 되시겠다. 그런데 얘네도 함수다.
한 번이라도 성공했거나 실패한 프로미스는 초기 상태로 돌아간다.
프로미스 생성자 함수에 인자로 들어간 함수 내부에서 우리는 비동기 작업을 하고, 비동기 작업이 성공할 경우 resolve를 실행해야 하고, 실패할 경우 reject를 실행해야 한다.
const promise = new Promise((resoleve, reject) => {
if (condition) {
resolve('stuff worked');
} else {
reject('Error);
}
});
function fetchUser() {
return new Promise((resolve, reject) => {
});
}
// Promise {<pending>}
// [[PromiseState]]: "pending"
// [[PromiseResult]]: undefined
위 상태에서는 promise가 pending만 되어있고 아무 일도 하지 않는다.
function fetchUser() {
return new Promise((resolve, reject) => {
resolve('hi');
});
}
var user = fetchUser();
console.log(user);
// Promise {<fulfilled>: "hi"}
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "hi"
위와같이 resolve 처리를 해주면 promise가 fulfilled로 바뀐다.
만약에 setTImeout이었다면 시간이 지나기 전은 pending, 시간이 지나고 resolve, reject 함수가 실행한 다음이면 상태가 성공/실패로 바뀐다.
function fetchUser() {
return new Promise((resolve, reject) => {
...
}
fetchUser().then(...
⏸
const fetchUser = new Promise(function(resolve, reject) {
...
};
fetchUser.then(...
⏸
// 익명
new Promise(function(resolve, reject) {
...
}.then(...
Reference JSON Placeholder urls
const urls = [
'https://jsonplaceholder.typicode.com/users', // 10users
'https://jsonplaceholder.typicode.com/posts', // 100posts
'https://jsonplaceholder.typicode.com/todos' // 200todos
]
Promise.all(urls.map(url => {
return fetch(url).then(response => response.json())
})).then(results => {
console.log(results[0])
console.log(results[1])
console.log(results[2])
}).catch(() => console.log('error'));
// users (10)[{...}, ... , {...}]
// posts (100)[{...}, ... , {...}]
// todos (200)[{...}, ... , {...}]
Remember the fetch simply returns a promise if I just run fetch here.
You see that I get a promise that's pending.
콘솔에 fetch('해당 url')을 찍으면 다음과 같이 출력된다.
💡
Body.json()
Body json() 메서드는 Response 스트링을 가져와 스트링이 완료될때까지 읽는다. 이 메서드는 body 텍스트를 JSON으로 바꾸는 결과로 해결되는 promise를 반환한다.
Return value
A promise that resolves with the result of parsing the body text as JSON.
This could be anything that can be represented by JSON — an object, an array, a string, a number... MDN
promise를 리턴하는 부분을 Producer,
State를 만드는 부분을 Consumer라고 나눈다.
Producer
promise를 만드는 순간 executor(callback)함수가 바로! 자동으로! 실행된다.
Consumer
resolve()
⇒ .then
reject()
⇒ .then
⇒ .catch
//실패시
const promise = new Promise((resolve, reject) => {
//doing some heavy work(network, read files)
console.log('hi')
setTimeout(() => {
//resolve('colki');
reject(new Error('no network'))
}, 2000);
});
promise.then((value) => {
console.log(value);
});
// hi
// (2초 후) Uncaught (in promise) Error: no network
⇒ then으로 성공적인 케이스에 대해서만 대응했기 때문에 uncaught에러가 발생!.
아래와 같이 catch
메서드를 사용해서 에러핸들링을 해주어야 한다.
promise
.then((value) => {
console.log(value);
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log('end');
});
// hi
// (2초 후) Error: no network
// end
finally는 성공과 실패 상관없이 무조건 마지막에 호출된다.
then은 값을 바로 전달해도 되고 또 다른 비동기인 promise를 전달해도 된다.
그래서 .then .then .then .catch 이렇게 여러개를 묶어서 사용 가능하다.
catch메서드로 앞서 나오는 에러를 한 번에 컨트롤할 수 있다.
자바스크립트에서 비동기 처리를 할 때는 콜백지옥을 만들어서 각각의 비동기함수마다 (err,data){ ...
식으로 처리해줘야 했다.
하지만 Promise가 도입되면서 then, catch를 사용해서 본래 자바스크립트에서 에러처리를 할 때처럼 native한 방식(try, catch)으로 다룰 수 있게 된 것이다.
개발자들에게 친숙한 에러핸들링일 뿐 아니라 성공로직과 실패로직을 명확하게 분리해서 핸들링할 수 있다.
또한 기존에는 비동기함수의 결과를 받을 때까지 아무것도 하지 못하고 마냥 기다려야 했다면, Promise인스턴스 객체를 반환해서 다른 함수에 넘겨주거나 자료구조에 담는 등 많은 일을 할수 있도록 길이 확장된 것이다.
그래서 Promise는 자바스크립트가 가지고 있던 제약에서 벗어나서, 수동적으로 처리하던 비동기를 동기적 흐름과 유사하게, 능동적으로 제어할 수 있게 해주는 역할을 한다. 우리가 쥐락 펴락 하기 수월해졌달까.
(그저 콜백지옥을 해결하기 위해 Promise가 나왔다? 는 비약으로 볼 수 있다)
#가독성 #확장성 #다양성 #능동적 #단순히 콜백지옥의 해결을 위해서 사용? #NO!
finally 메서드는 promise 객체를 리턴한다. promise가 resolve되거나 reject 되는 것 관계 없이 finally에 지정한 콜백함수가 무조건 실행된다.
promise 결과에 상관없이 무조건 한 번은 실행된다는 소리!
.finally(콜백함수)
const urls = [
'http://swapi.dev/api/people/1',
'http://swapi.dev/api/people/2',
'http://swapi.dev/api/people/3',
'http://swapi.dev/api/people/4'
];
Promise.all(urls.map(url => {
return fetch(url).then(people => people.json())
}))
.then(people => {
console.log('1', people[0])
//throw Error;
console.log('2', people[1])
console.log('3', people[2])
console.log('4', people[3])
})
.catch(err => console.log('uhh fix it!', err))
.finally(() => {console.log('final')})
//final
에러 코드를 넣든 안넣든 콜백함수가 무조건 실행되기 때문에, 항상 final이 출력된다.
multiple promises를 처리하는 3가지 방법이 있다.
// 3개의 Promise를 각 방법으로 돌려보자.
const promisify = (item, delay) =>
new Promise((resolve) =>
setTimeout(() =>
resolve(item), delay));
const a = () => promisify('a', 100);
const b = () => promisify('b', 5000);
const c = () => promisify('c', 3000);
1번, 2번, 3번 동시에 타이머 시작!
async function parallel() {
const promises = [a(), b(), c()]; // [Promise {}, Promise {}, Promise {}]
const [output1, output2, output3] = await Promise.all(promises);
return `parallel is done: ${output1} ${output2} ${output3}`;
}
parallel().then(console.log);
// parallel is done: a b c
1번 끝나면 다음 2번 끝나면 다음 3번 끝나면 뙇!
지저분할 수 있는 Promise Chaining을 보기 좋게 만들어주는 nice한 방법.
const output1 = await a();
// a값이 리턴될 때까지 동작을 멈추고 있음 리턴값나오면
// 다음 구문 b값 리턴될 때까지 또 멈추고 기다림~ 반복~
waiting for all the things to settle and then finally eventually we'll get sequences done a b c.
async function sequence() {
const output1 = await a();
const output2 = await b();
const output3 = await c();
return `sequence is done ${output1} ${output2} ${output3}`;
}
sequence().then(console.log);
// sequence is done a b c
제일 먼저 1등으로 들어오는 놈만 뙇!
whichever one returns first. I wanted to be upper one and just log that and ignore all the other ones
async function race() {
const promises = [a(), b(), c()]; // [Promise {}, Promise {}, Promise {}]
const output1 = await Promise.race(promises);
return `race is done ${output1}`;
}
race().then(console.log);
// race is done a
누가 제일 빠를까?
parallel().then(console.log);
sequence().then(console.log);
race().then(console.log);
// race is done a (100ms 후)
// parallel is done: a b c (500ms후)
// sequence is done a b c (8100ms 후)
const promiseOne = new Promise((resolve, reject) =>
setTimeout(resolve, 3000));
const promiseTwo = new Promise((resolve, reject) =>
setTimeout(reject, 3000));
Promise.all([promiseOne, promiseTwo]).then(console.log);
3000ms후에 resolve와 reject를 실행하는 두 코드가 공존할 때
무슨 일이 벌어질까?
Promise.all() 안에는 성공의 케이스만 들어가야 하는데, 현재 resolve일때, reject일때 두 경우가 함께 들어있고 또한 .catch구문이 없어서 에러핸들링을 못 해주고 있다.
⏬ 리팩토링: all → allSettled
& add .catch
Promise.allSettled([promiseOne, promiseTwo])
.then(console.log)
.catch(err => console.log('something failed', err));
promise.allSettled로 메서드를 바꿔주니 fulfilled value와 reject reason 의 각 state 담긴 Promise 객체가 리턴되었다.
그렇다면, all()과 allSettled() 두 메서드의 차이는 무엇일까?
Promise.all()
여러 Promise 의 성공/실패 여부에 따라서 결과가 달라진다.
모두 성공일 때 or Promise 가 없는 경우 : Promise 객체가 담긴 배열을 리턴한다.
하나라도 실패일 때 : reject일때의 reason을 전달한다.
Promise.allSettled()
runs regardless of whether they reject or not.
성공 또는 실패의 결과를 모아서 Promise 객체가 담긴 배열을 리턴한다.
fulfilled,상태로 전달되면 value 속성이 전달되고, rejected 상태로 전달 시 reason 속성으로 전달된다.
그래서 각 Promise가 어떻게 이행(또는 거부)됐는지 value 속성 및 reason 속성을 통해 알 수 있다.