개인노트-인강

개인노트 nomad ES6의 정석 #8 Promises

roroism 2023. 2. 15. 21:14

# 8.0 Introduction to Async

 

Async : 비동기성

 

인간은 순차적으로 한번에 하나씩 실행하지만, javascript는 동시에 여러가지 일을 할 수 있습니다.

 

## 비유

 

1.포탄을 넣는다.
2.장약을 넣는다.
3.약실을 닫는다.
4.격발을 한다.


이것이 일반적인 인간의 워크플로우지만 컴퓨터는 딱히 순서가 필요없이 멀티태스킹을 하면 된다. 그러나 문제는 어떤 작업이 언제 끝날지 알수 없다는 것이다. 장약을 넣는작업이 맨 마지막에 끝난다면 포는 사격되지 않는다.

 

# 8.1 Creating Promises

 

Promise는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

 

const amISexy = new Promise();

 

그래서 Promise를 만들 때는 실행할 수 있는 function을 넣어야합니다.

Promise를 호출할 때, 자바스크립트는 우리가 설정한 다른 function과(executor) 함께 이 Promise를 실행합니다.

const amISexy = new Promise((resolve, reject) => {
	// 할 일.
});

resolve : "야, 이게 네 값이야. 자바스크립트로 돌아가."

reject : "야, 미안한데 에러가 있어."

 

const amISexy = new Promise((resolve, reject) => {

});

console.log(amISexy);
// 결과
// Promise {<pending>}

pending이라는 상태값이 출력되면서 함수가 끝나지 않고, 계속 대기중입니다.

 

이 Promise를 끝내기 위해서는 resolve function을 실행하는 것입니다.

그러면 Promise를 resolve할 것이고, Promise를 끝낼 것입니다. 그리고 값을 얻게 될 것입니다.

const amISexy = new Promise((resolve, reject) => {
	setTimeout(resolve, 3000, "Yes you are");
});

console.log(amISexy);

setInterval(console.log, 1000, amISexy);
// 결과
// Promise {<pending>}		app.js:5
// Promise {<pending>}
// Promise {<pending>}
// Promise {<resolved>: "Yes you are"}
// Promise {<resolved>: "Yes you are"}
// ...

 

Promise의 핵심은 우리가 아직 모르는 value와 함께 작업할 수 있게 해준다는 것입니다.

자바스크립트: "야, 이걸 해줘. 이건 바로 안 될 거야. 이걸 하고, 끝나면 다시 알려줘."

 

여기서 만약,

Promise가 API를 호출한다면 어떻게 할까요?

Promise가 파일 시스템에서 파일을 연다면?

Promise가 유저의 설정을 연다면?

로딩이 다 되면 그걸 다시 자바스크립트에게 돌려줘야 합니다.

 

다음은 Promise를 어떻게 사용할지에 대해서 배웁니다.

 

# 8.2 Using Promises

 

Promise를 사용하려면 Then을 불러와야합니다.

Then의 사용법을 알아봅니다.

 

## then

 

자바스크립트에 promise 가 끝난 이후의 명령어를 전달하려면,

명령이 끝나고 나서 값을 돌려달라고 명령어를 내려야 합니다.

const amISexy = new Promise((resolve, reject) => {
	setTimeout(resolve, 3000, "Yes you are");
});

amISexy.then(value => console.log(value));

 

function으로 따로 선언하면..

const amISexy = new Promise((resolve, reject) => {
	setTimeout(resolve, 3000, "Yes you are");
});

const thenFn = value => console.log(value);

amISexy.then(thenFn);

 

## catch

 

promise에 error가 생기면 catch로 잡을 수 있습니다.

먼저 reject로 에러를 발생시켜봅니다.

const amISexy = new Promise((resolve, reject) => {
	setTimeout(reject, 3000, "Yes you ugly");
});

const thenFn = value => console.log(value);

amISexy.then(thenFn);

 

catch는 then이랑 비슷하지만 에러를 위해 사용합니다.

const amISexy = new Promise((resolve, reject) => {
	setTimeout(reject, 3000, "Yes you ugly");
});

amISexy
    .then(result => console.log(value))
    .catch(error => console.log(value));
// 출력
// You are ugly

then과 catch는 순서대로 실행되지 않고, 각각 다른 상황에서 실행됩니다. 한 쪽이 실행되면 다른 한 쪽은 실행되지 않습니다.

 

# 8.3 Chaining Promises

 

가끔은 Promise를 chaining으로 사용해야 할 때가 있습니다.

예를 들어 API로 가서 암호화된 data를 받고, 암호화된 data를 풀고, 파일로 저장할 때, 총 then을 3번 사용하고 이를 순서대로 이어주어야 합니다.

모든 then은 서로의 순서가 끝나기만을 기다립니다.

const amIsexy = new Promise((resolve, reject) => {
    resolve(2);
});

amIsexy
    .then(number => {
        console.log(number * 2);
    })
    .then(othernumber => {
        console.log(othernumber);
    });
// 결과
// 4
// undefined

promise들을 엮고 싶을 때는 이전의 then에서 return 값이 있어야 합니다.

위에서 첫번째 then에서 return이 없기 때문에 undefined를 반환하여 다음 then에 전달되었습니다.

 

아래처럼 return값을 다음 then에 전달하면..

const amIsexy = new Promise((resolve, reject) => {
    resolve(2);
});

amIsexy
    .then(number => {
        console.log(number * 2);
        return number * 2;
    })
    .then(othernumber => {
        console.log(othernumber * 2);
    });
// 결과
// 4
// 8

 

function으로 묶어서 반복하는 경우..

const amIdouble = new Promise((resolve, reject) => {
    resolve(2);
});

const timesTwo = (numbertwo) => numbertwo * 2;

amIdouble
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(lastNumber => console.log(lastNumber));
// 결과
// 64

 

하나의 then에 에러가 발생한다면..?

충분히 일어날 수 있는 일입니다. 가끔 많은 단계가 일어나야 할 때, 그 중 하나에 문제가 생겨서 에러가 나는 경우도 있습니다.

const amIdouble = new Promise((resolve, reject) => {
    resolve(2);
});

const timesTwo = (numbertwo) => numbertwo * 2;

amIdouble
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(() => { throw Error("Something is wrong") })
    .then(lastNumber => console.log(lastNumber));
// 결과

lastNumber까지 진행되지 않았습니다.

여기서 error를 catch 한다면?

const amIdouble = new Promise((resolve, reject) => {
    resolve(2);
});

const timesTwo = (numbertwo) => numbertwo * 2;

amIdouble
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(() => { throw Error("Something is wrong") })
    .then(lastNumber => console.log(lastNumber))
    .catch(error => console.log(error));
// 결과

catch가 다른 모든 then이랑 function안의 에러들까지 다 잡아줍니다.

const amIdouble = new Promise((resolve, reject) => {
    // resolve(2);
    reject(2);
});

const timesTwo = (numbertwo) => numbertwo * 2;

amIdouble
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(timesTwo)
    .then(() => { throw Error("Something is wrong") })
    .then(lastNumber => console.log(lastNumber))
    .catch(error => console.log(error));
// 결과
// 2

 

# 8.4 Promise.all

 

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 하나의 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부합니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

 

가끔 하나의 API가 아닌 3개, 4개의 API에서 값을 불러와야 할 때가 있습니다.

const p1 = new Promise((resolve) => {
    setTimeout(resolve, 5000, "First");
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, "second");
});

const p3 = new Promise((resolve) => {
    setTimeout(resolve, 3000, "third");
});

이것을 p1 then, p2 then, p3 이런식으로 수많은 then을 넣어주는 것 보다는 Promise.all을 사용하면 더 간편합니다.

 

Promise.all은 한 개의 Promise를 리턴값으로 줍니다. 모든 Promise가 전부 Resolve 되고 나면 마지막 Promise를 리턴값으로 줍니다.

const p1 = new Promise((resolve) => {
    setTimeout(resolve, 5000, "First");
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, "second");
});

const p3 = new Promise((resolve) => {
    setTimeout(resolve, 3000, "third");
});

const motherPromise = Promise.all([p1, p2, p3]);

motherPromise.then(values => console.log("values"));
// 결과 (약 5초 뒤)
// ["First", "Second", "Third"]

 

Promise.all이 다른 promise 들이 전부 진행 될 때까지 기다렸다가 진행됩니다.

그리고 각각 언제 끝나는지 상관없이 값들을 순서에 맞춰서 얻게 됩니다. p1이 예상으로 반환된 Array에서 제일 마지막 값이 되어야 하지만, Promise.all은 순서에 맞춰서 Array를 반환합니다.

 

만약 이 중 하나가 reject 된다면?

const p1 = new Promise((resolve) => {
    setTimeout(resolve, 5000, "First");
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(reject, 1000, "I hate JS");
});

const p3 = new Promise((resolve) => {
    setTimeout(resolve, 3000, "third");
});

const motherPromise = Promise.all([p1, p2, p3]);

motherPromise.then(values => console.log("values"));
// 결과

위에서의 Promise.all 은 요청한 대로 3개의 Promise들이 필요한데 그 중 하나라도 reject되어 3개의 promise를 반환 받지 못한다면 motherPromise도 reject 됩니다.

// ...
motherPromise
    .then(values => console.log("values"))
    .catch(err => console.log(err));
// 결과

3개의 Promise 중 하나라도 reject 되면 다른 promise 들도 같이 reject 됩니다.

 

promise 들이 전부 제대로 동작되어야 할 때 사용하면 좋습니다.

 

# 8.5 Promise.race()

 

Promise.race() 메소드는 Promise 객체를 반환합니다. 이 프로미스 객체는 iterable 안에 있는 프로미스 중에 가장 먼저 완료된 것의 결과값으로 그대로 이행하거나 거부합니다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/race

 

const p1 = new Promise((resolve) => {
    setTimeout(resolve, 5000, "First");
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(reject, 1000, "I hate JS");
});

const p3 = new Promise((resolve) => {
    setTimeout(resolve, 3000, "third");
});

const motherPromise = Promise.race([p1, p2, p3]);

motherPromise
    .then(values => console.log(values))
    .catch(err => console.log(err));
// 결과
// I hate JS

Promise.race() 는 Promise.all() 과 사용법은 같습니다.

Promise.race()의 다른 점은 위의 코드를 예시로.. Promise 3개 중에 하나라도 resolve 되거나 reject가 되면 결과가 나옵니다.

Promise.race()가 resolve 되어서 then으로 넘어가거나 reject 되어서 catch로 넘어가려면 p1, p2, p3 중 하나만 resolve 되거나 reject 되면 됩니다.

즉, 가장 빠른 것으로 결과를 정합니다.

Race는 어떤 것이든지 성공하거나, reject가 발생하지 않으면 resolve됩니다.

 

## 사용

 

어느 것이 먼저 되는지 상관 없을 때 Race를 사용하면 됩니다. 

 

# 8.6 finally

 

const p1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 10000, "First");
})
    .then(value => console.log(value))
    .catch(e => console.log(`${e}❌`))
    .finally(() => console.log("Im done"));
// 결과
// First
// Im done

nico는 보통 finally를 보통 API 호출할 때 사용합니다.

로딩할 때. 또는 하나를 얻고 두 개를 얻고 마지막으로 데이터를 보여주거나. 또는 로딩을 멈추거나 등.

 

# 8.7 Real world Promises

 

fetch는 promise를 return 합니다.

fetch는 무언가를 가지고 오는 역할을 합니다.

 

fetch하고 나서 then에 response를 가지고 와서 console.log를 해보겠습니다.

fetch("https://google.com")
    .then(response => console.log(response))
    .catch(e => console.log(`❌ ${e}`));
// 결과
// ❌ TypeError: Failed to fetch

google에서 막았기 때문에 에러가 발생합니다.

영화목록을 보내주는 api를 fetch한다면..

fetch("https://yts.am/api/v2/list_movies.json")
    .then(response => {
        console.log(response);
        return response.json(); // response객체에 .json()을 실행하면 Promise로 반환합니다.
    })
    .then(json => console.log(json))
    .catch(e => console.log(`❌ ${e}`));
// 결과

 

response 에는 Promise를 기반으로 하는 다양한 메서드가 있습니다. 이 메서드들을 사용하면 다양한 형태의 응답 본문을 처리할 수 있습니다. (예 : response.json() response.text() ...)

 

보통은 우리만의 Promise를 만들지는 않습니다. fetch를 사용한 것처럼 다른 사람이 만든 Promise를 사용하게됩니다.