프로미스는 하나의 상자라고 볼 수 있음. 프로미스라는 상자는 비동기 작업이 시작될 때 만들어짐. 처음엔 상자가 텅 비어있다가 언젠가 결과물로 채워지게 됨. (비동기 작업이 완료될 때)

비동기 작업은 완료되기까지 시간이 좀 걸린다는 특징이 있음. 비동기 작업이 진행 중일 때는 프로미스 상자가 텅 비어있음. 그러다가 비동기 작업이 끝나면, 그 결과물로 프로미스 상자가 채워지게 됨. 이처럼 프로미스는 비동기 작업의 상태를 나타냄!
진행 중인 비동기 작업은 언젠가 끝나게 됨. 작업이 성공할 수도 있고, 실패할 수도 있음. 예를 들어 서버로부터 데이터를 받아오는 비동기 작업이라고 가정했을 때, 데이터를 성공적으로 받아오면 그 작업은 성공한 것. 중간에 네트워크 에러가 나서 데이터를 받아오지 못하면 작업이 실패한 것임.
비동기 작업이 실행 중일 때는 프로미스 상자가 비어있음. 프로미스 상자에는 딱지가 하나 붙어 있는데, 프로미스 상자의 현재 상태가 적혀 있음.
시간이 지나고 진행 중이던 작업이 끝나면 성공, 실패 둘 중 하나로 결정된다.
그리고 상자 안에는 비동기 작업의 결과값이 들어가게 됨.
상자 안에는 작업 중에 발생한 에러가 들어가게 됨.

이처럼 Promise는 비동기 작업이 맞이할 성공 또는 실패를 나타내는 상자!
프로미스라는 상자는 사실 자바스크립트 객체임. Promise 객체는 state와 result라는 두 가지 속성을 가지고 있음. state는 상태, result는 결과물을 의미함. 프로미스 상자에 붙어 있는 딱지가 state. 상자 안 결과물이 result인 셈.
1. Pending(대기)
2. Fulfilled(성공)
3. Rejected(실패)
이렇게 세 가지 종류의 딱지 중 하나의 딱지를 가질 수 있는 것임. 이 세 가지는 각각 비동기 작업의 대기, 성공, 실패 상태를 의미함.
비동기 작업이 진행 중일 때 Promise 객체는 Pending state를 가지고 있음. 작업이 성공하지도 실패하지도 않은 대기 상태. 이때, promise의 result는 'undefined'.

비동기 작업이 성공적으로 완료되면 Promise 객체는 fulfilled state를 갖게 됨. 이때 result는 결과값으로 채워지게 되는데, 비동기 작업이 반환한 결과값이 됨. 예를 들어 서버에서 데이터를 받아오는 작업이라면 받아온 데이터가 Promise의 result가 되는 것임.

비동기 작업이 진행되다가 문제가 발생해 실패하게 되면 Promise 객체는 rejected state를 갖게 됨. 비동기 작업이 실패하면 Promise의 result로는 에러 객체가 들어가게 됨. 에러를 보고 비동기 작업이 왜 실패한 것인지 알 수 있음. 
이처럼 Promise 객체는 항상 pending state에서 시작해서 비동기 작업의 결과에 따라 fulfilled state 또는 rejected state가 된다. 현재 Promise 객체가 가지는 state에 따라 result 또한 달라지게 된다. 
Promise 객체는 'new' 키워드를 사용해서 만들 수 있음.
Promise의 생성자는 함수를 하나 전달 받는데, 그 함수를 'executor 함수'라고 함. 이 함수 안에 내가 원하는 비동기 작업 코드를 작성하면 됨. 익시큐터 함수는 두 가지 인자를 전달 받음. resolve, reject!

출력됨.
여기서 알 수 있는 점은, new 키워드를 사용해서 promise 객체가 생성되는 그 즉시 익시큐터 함수가 실행된다는 것. 그렇다면 익시큐터 함수가 전달 받는 resolve, reject는 뭘까? 이들은 익시큐터 함수 안에서 호출해줄 수 있는 함수임. 내가 하고자 하는 비동기 작업이 성공하면 resolve, 실패하면 reject를 호출.

1초 뒤 출력. 왜냐하면 익시큐터 함수는 프로미스 객체가 생성되는 그 즉시 실행되기 때문.
내가 만든 프로미스 객체가 어떻게 생겼는지 보고 싶다면,


내가 만든 프로미스 객체가 콘솔에 출력되었음.

promise의 state는 pending. result는 undefined. 대기라는 딱지가 붙어 있는 텅 빈 상자임. 내가 아직 프로미스한테 비동기 작업이 완료되었다는 것을 알려주지 않았기 때문에 아직 대기 상태. 1초가 지나고 데이터를 받아오는 것이 완료되면 프로미스한테 작업이 완료되었다고 알려주어야 함. 인자로 전달받은 resolve를 사용하면 됨. 익시큐터 함수에 인자로 들어오는 resolve는 비동기 작업이 완료됐다 라고 말해주는 함수임. 1초가 지난 다음 데이터를 성공적으로 받아오면, resolve 함수를 호출하면 됨. resolve의 인자로는 비동기 작업의 결과물을 넣어주면 됨. 위 코드의 경우, result는 'data'가 될 것임. 이제 resolve가 된 이후 프로미스가 어떻게 생겼는지 확인할 것임. 프로미스는 1초가 지난 후 resolve가 되니까 2초 뒤에 프로미스가 어떻게 생겼는지 확인하면 됨. setTimeout을 한 번 더 사용하기.



열어보면 프로미스의 state는 성공을 나타내는 fulfilled. result는 내가 넣어준 객체가 됨. 성공 딱지가 붙은 상자 안에 비동기 작업의 결과물인 데이터가 들어가 있는 것.
이번엔 비동기 작업이 실패하는 경우를 생각해보자. 만약 서버에서 데이터를 가져오다가 문제가 생겨 데이터가 null이 되었다고 가정. 만약 데이터가 성공적으로 받아와져서 데이터가 null이 아니라면 resolve를 if문 안에서 호출해주고, 데이터가 null이라면 else문 코드를 실행하면 됨. 이제 비동기 작업이 실패했다는 것을 프로미스에게 알려주어야 함. 그럴 때 익시큐터 함수에 전달된 두 번째 인자인 reject를 호출하면 됨. reject의 인자는 비동기 작업이 왜 실패했는지를 알려주는 에러 객체를 넣어주면 됨. 이제 비동기 작업이 실패했을 때 프로미스가 어떻게 생겼는지 확인해보자.



열어보면, promise의 state는 rejected. result는 넣어준 에러 객체가 들어감. 실패 딱지가 붙은 상자에 에러가 들어가 있음.
프로미스는 생성되는 그 즉시 전달해준 익시큐터 함수를 호출시킨다. 하지만 나는 이런 비동기 작업을 비동기 함수로 만들고 싶음. 함수를 하나 만들고, 그 내부에서 promise를 생성해주면 됨.
getData라는 함수를 하나 만들고 그 내부에 아까 짰던 코드를 복붙하기. 
함수(getData) 안에서 프로미스 객체를 만들어주면 함수(getData)가 호출되면 프로미스 객체가 만들어지고 익시큐터 함수가 호출되면서 비동기 작업이 시작된다. 마지막에 프로미스를 리턴해보겠음. 내가 만든 getData 함수를 호출하는 입장에서 비동기 작업이 어떻게 진행되고 있는지 알아야 하기 때문. 이를 통해 대기, 성공, 실패 중 어느 상태인지 파악 가능. 

잘 출력됨.
지금까지는 비동기 함수를 구현하는 입장에서 프로미스를 사용했음.
그럼 이제 프로미스를 리턴하는 비동기 함수를 사용하는 입장이 되어보자.
비동기 함수를 의미 있게 사용하기 위해서는 비동기 작업이 다 끝날 때까지 기다릴 수 있어야 함. 만약, 비동기 작업이 성공적으로 완료되었다면 그에 따른 처리를 할 수 있어야 함. 또한 실패했다면 적절한 에러 처리를 할 수 있어야 함. 이 모든 것들을 비동기 함수로부터 전달받은 promise 객체를 사용해서 간편하게 할 수 있음.
promise 객체는 then, catch, finally라는 API를 제공해줌. 이것들을 사용해서 비동기 작업에 대한 후처리를 간편하게 할 수 있음.
비동기 작업이 성공적으로 끝나면, promise는 fulfilled state가 됨. 
result 안에 들어가 있는 데이터를 받아서 사용할 수 있어야 함. 내가 전달 받은 프로미스 객체는 'then'이라는 메서드를 가지고 있음. 이 메서드는 콜백 함수를 전달 받음. then 안에다가 비동기 작업의 후처리를 해주면 됨. 
비동기 작업이 완료되고 나서 then 안에 있는 콜백 함수가 실행됨. then은 비동기 작업이 완료될 때까지 기다렸다가 fulfilled 상태로 바뀌면, then 안에다 전달해준 콜백 함수를 호출해줌. 콜백 함수는 매개 변수 하나를 전달 받는데, 프로미스의 result임. 

잘 출력됨.
더 간단한 코드 작성법.

이제 then 안에서 비동기 작업이 성공적으로 완료된 후에 수행해야 하는 후처리 코드를 작성할 수 있음. 

이번엔 비동기 처리 도중에 문제가 생겨 프로미스가 실패하는 경우를 생각해보자. promise의 state는 rejected가 되고, result는 에러 객체임. 

에러에 대한 후처리를 하지 않았으므로 아직 에러가 뜸. 에러에 대한 후처리를 해주기 위해서는 프로미스의 'catch'를 사용하면 됨. catch도 콜백 함수 하나 받음. catch 내부에 프로미스가 실패한 경우, 에러처리를 해줄 수 있음. catch를 붙여주면 비동기 작업을 처리하다가 문제가 발생해서 프로미스가 실패하면 catch 안에 넣어준 콜백 함수를 실행한다. catch 내부에 있는 콜백 함수도 인자를 하나 받는데, 'error'임. 
에러를 출력시켜보면,

에러가 잘 출력됨.
근데 굳이 에러를 출력할 필요는 없으니까 다른 메시지 넣어보자.

getData를 호출하면, getData는 프로미스를 리턴하고 비동기 작업이 성공해서 프로미스가 fulfilled가 되면, then 안에 들어간 콜백 함수가 호출이 되고, 만약 중간에 문제가 발생해서 프로미스가 rejected가 되면 catch 안에 있는 콜백 함수가 호출된다.
프로미스가 성공하든 실패하든 상관 없이 무조건 수행되어야 하는 코드가 있을 때는 finally를 사용하면 됨. 프로미스 맨 뒤에 finally를 붙여주면 됨. 안에 콜백 함수를 전달해주면, 콜백 함수는 프로미스의 성공 여부와는 관계 없이 제일 마지막에 호출됨. 비동기 작업의 성공/실패 여부와는 관계 없이 반드시 실행되어야 하는 코드가 있다면 finally 안에 넣으면 됨! 

프로미스가 실패해도 '마무리 작업'이 실행됨.
그럼 getData를 성공하도록 만들어보자. 

프로미스가 성공한 다음에도 '마무리 작업'이 실행됨.
비동기 작업이 성공적으로 완료된다면, then이 실행되고 그 다음 finally가 실행된다. 만약 비동기 작업이 실패한다면 catch가 실행되고 finally가 실행된다.
프로미스의 then은 항상 새로운 프로미스를 리턴해줌. 그렇기에 then 뒤에 또다른 then을 연결할 수 있음. 
이렇게 체인 형태로 then을 연결하는 기법을 'Promise chaining'이라고 한다. 이를 사용하면 여러 개의 비동기 작업을 순서대로 수행할 수 있음. 
받아온 데이터를 출력시키면,
데이터가 잘 출력됨.
만약, 데이터를 다 받아온 다음에 한 번 더 데이터를 받아와야 한다면 then 안에다 getData()를 한 번 더 호출하면 된다. 또 호출한 getData는 프로미스를 또 리턴할 것임. 이걸 then 안에 있는 콜백 함수에서 그대로 리턴해주고 then 다음에 또다른 then을 붙여서 똑같이 데이터를 출력해주면, 첫번째 then안에서 반환된 프로미스가 resolve될 때까지 기다렸다가 다음에 오는 then 블록이 실행된다. 
첫 번째 then 블록이 실행돼서 받아온 데이터가 출력된 다음, getData가 호출이 되고 나서 여기서 리턴해준 프로미스가 완료될 때까지 기다렸다가 그다음 then 블록이 실행돼서 콘솔에 데이터가 잘 출력됨.

이런 식으로 계속 붙여주면 됨.

이처럼 promise chaining을 사용하면 여러 개의 비동기 함수를 순차적으로 수행해야 될 때 깔끔하게 코드를 작성할 수 있다. console.log들을 다 지우고 문법도 조금 생략해보면 깔끔해질 것임. 
비동기 콜백에 비해 확실히 가독성이 좋아짐. 콜백 함수의 중첩이 필요없게 됨.
promise chaining을 사용하기 위해 then 안에서 프로미스를 리턴해줬음. 만약 프로미스를 리턴하는 것이 아니라 어떠한 값을 리턴한다면? 예를 들어 프로미스가 아니라 'hello' 이런 문자열 같은 값을 리턴하면? 값을 리턴해주더라도 then은 항상 프로미스를 리턴해준다. 이렇게 리턴된 값(hello)은 프로미스로 감싸져서 곧바로 resolve가 된 상태가 됨. 그리고 나서 그 다음 오는 then으로 넘겨줌. 두 번째 then 안에서 데이터를 받아서 출력해보면, 

첫 번째 console.log에서 데이터가 출력되고 'developer'를 리턴해주면 두 번째 console.log에서 그대로 'developer'가 출력된다. 이렇게 then 안에서 어떤 값을 리턴해주면 그 값은 프로미스로 감싸져서 바로 resolve된 상태로 다음 then으로 넘어가기 때문에 프로미스가 resolve될 때까지 기다릴 필요 없이 즉시 사용할 수 있는 것임.
Fetch API는 특정 URL로 네트워크 요청을 보내는 비동기 함수임. 특정 서버로부터 데이터를 받아오는 일을 할 때 많이 사용. Fetch API는 프로미스를 리턴하도록 구현되어 있음. 그렇기 때문에 Fetch 함수를 호출하여 then, catch, finally를 사용해서 다양하게 후처리를 해줄 수 있음.
Fetch를 간단하게 사용하는 방법은 인자로 URL을 집어넣어주면 되는데, 네트워크 요청을 보낼 서버의 URL임. 문자열 형태로 넣어주면 됨. 이번 코드의 URL로 요청을 보내면, 샘플 사용자 데이터를 받을 수 있음. Fetch는 프로미스를 리턴하기 때문에 뒤에다 then을 붙여줄 수 있음. then의 인자로는 서버로부터 받아온 response가 들어감. Fetch가 서버로부터 응답을 받아오면 프로미스가 resolve가 되어서 fulfilled 상태로 바뀜. 물론 문제가 발생하면 rejected 상태가 됨. 이렇게 응답을 받아오길 완료하면 그 다음 오는 then 블록이 실행됨. 받아온 response를 출력해보겠음. 

이제 response에서 받아온 데이터를 추출해야 함. 그러기 위해서는 response 객체에 json이라는 메서드를 호출하면 됨. json 메서드도 프로미스를 반환해줌. 그래서 프로미스를 리턴해줄 것. 그리고 다음 then에서 추출된 데이터를 받아오고 출력해보면 됨. 

사용자 데이터가 받아와짐. 만약 fetch를 하다가 문제가 발생할 것을 대비한다면 뒤에 catch를 붙여주면 됨. 
fetch가 성공하든 실패하든 무조건 실행되어야 하는 마무리 작업이 있다면 finallly를 붙여주면 됨. 

지난 게시물에서는 비동기 콜백을 사용해서 해봤음. -> 콜백지옥 경험

이 코드를 가지고 비동기 콜백을 사용해서 지저분한 코드를 만들었었음.
프로미스로 바꿔보자!
비동기 콜백을 사용하지 않을 것이므로 callback 인자를 지워주고 프로미스를 리턴하도록 만들면 됨. 익시큐터 함수를 만들어주고 그 안에 비동기 함수를 넣어주면 됨. 이제 1초 뒤에 콜백 함수를 수행해주는 게 아니라 resolve를 호출해주면 됨. resolve의 인자로는 username을 넣어주면 됨. 근데 이거는 username이 존재할 경우에만 성공했다고 알려주는 resolve를 호출해야 하고, username이 존재하지 않는다면 reject를 사용해서 error 객체를 넣어주면 됨. 이제 로그인 함수를 사용해보겠음. username 아무거나 넣어주고, 로그인이 끝난 다음에는 then을 사용해서 후처리를 실행해줄 콜백 함수를 넣으면 됨. 

1초 뒤에 잘 출력됨!

로그인에 실패하는 경우, 뒤에 catch를 붙여서 처리하면 됨.




내가 작성한 온라인 쇼핑몰 시나리오는 각각의 함수가 순차적으로 진행되어야 함. 로그인이 끝나면 장바구니에 담고, 결제. -> promise chaining 사용하기.


이렇게 promise chaining을 사용해서 세 개의 비동기 함수를 순차적으로 수행할 수 있었음. 로그인이 끝날 때까지 기다렸다가 addToCart 실행, 또 기다렸다가 getPayment 실행. 만약 에러 처리를 하고 싶으면 뒤에 catch를 넣으면 됨. 만약 addToCart에 '토트넘 유니폼'이 아니라 빈 문자열을 전달해주면 addToCart가 실패해서 나머지 then들은 스킵하고 바로 catch 코드가 실행된다. 

오류를 고치고 싶다면?


잘 출력됨!


finally까지 잘 완료.
console.log 다 지워주고 문법도 조금 생략해보면,

콜백 함수보다 훨씬 가독성 좋음.