일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 알고리즘 문제
- MAC
- API
- CSS
- AWS
- web
- HTML
- PYTHON
- js
- django ORM
- 파이썬
- java
- DRF
- 장고
- react
- 백준
- 알고리즘 풀이
- django rest framework
- c++
- Algorithm
- Baekjoon
- 파이썬 알고리즘
- javascript
- 알고리즘 연습
- form
- Django
- es6
- django widget
- 알고리즘
- Git
- Today
- Total
수학과의 좌충우돌 프로그래밍
[ES6] Promises - then, catch, all, race, finally 본문
이번 포스팅에서는 js 의 Promises
에 대해서 알아보도록 하겠습니다.
javascript 의 비동기성
사람은 한 번에 두 가지 일을 할 수가 없습니다.
흔히들 말하는 멀티태스킹도 실제로는 한 번에 두 가지 일을 동시에 하는 것이 아니라 빠르게 스위칭해서 동시에 일어나는 것 처럼 보일 뿐입니다.
그에 비해 컴퓨터는 동시에 두 가지 일을 할 수 있습니다. 두 가지 뿐만 아니라 그 이상도 가능하죠.
영상을 녹화하며 충전을 하고 오디오를 녹음하고 사용자로 부터 입력을 받고 시간이 바뀌는 것을 기다리는 것이 가능합니다.
자바스크립트도 이와 같습니다. 동시에 많은 일을 하는게 가능합니다.
한 쪽에서 다른 일을 하고 있어도 프로그램은 계속 실행이 됩니다.
예시를 통해 알아보도록 하겠습니다.
다음과 같이 fetch 를 한 후 console.log 를 찍으면 fetch 에서 에러가 발생하기 때문에 끝나야하는데 실제 결과에서는 example 이 찍히고 에러가 발생합니다.
이러한 이유가 발생하는 것이 바로 자바스크립트의 비동기성입니다.
fetch 를 통해 먼저 요청을 보내놓고 그 쪽에서는 일을 진행하고 있지만 프로그램은 계속 실행이 되므로 console.log 를 출력하는 것이죠. 그리고 요청의 결과, 에러가 발생했기 때문에 그 다음에 에러가 나는 것이고요.
const example = fetch("http://example.com/");
console.log("example");
// example
// Uncaught (in promise) TypeError: Failed to fetch
Promise 란?
Promise
개체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.
-
매개변수
우선
Promise
는 매개변수로executor
를 받게 됩니다.executor
는resolve
와reject
인수를 전달할 실행함수입니다.resolve
와reject
는 함수로서 호출되면promise
를 이행하거나 거부합니다. 이 둘을 이용하여 비동기 작업이 모두 끝나면resolve
를 호출해 이행하고, 중간에 오류가 생기면reject
를 이용해 거부하게 됩니다. -
상태
Promise
는 3가지 상태를 가질 수 있습니다.- pedding : 대기 상태로서 아직
resolve
할지reject
할 지 결정되지 않은 초기의 상태입니다. - fullfilled : 이행 상태로서 연산이 성공적으로 완료된 상태입니다.
- rejected : 거부 상태로서 연산이 실패한 상태입니다.
- pedding : 대기 상태로서 아직
Promise
를 사용하게 되면 아직 모르는 값들과 함께 작업을 할 수 있게 됩니다.
Promise
를 사용해봅시다.
executor
로 arrow function 을 사용했고 이는resolve
와 reject
, 두 함수를 매개변수로 받습니다.
그 후 결과를 확인하면 아직 어느 것도 결정되지 않았으므로 pedding
상태입니다.
const promise = new Promise((resolve, reject)=>{});
console.log(promise);
// Promise { <pending> }
promise
는 3초 후에 resolve
하게 되고 밖에서는 1초마다 promise
를 실행해보았습니다.
3번째 까지는 pending
상태고 그 이후에는 resolve
의 매개변수로 들어간 string이 출력이 되는 걸 확인할 수 있습니다.
const promise = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve("resolve");
}, 3000);
});
console.log(promise);
setInterval(()=>{
console.log(promise);
}, 1000);
/*
Promise { <pending> }
Promise { <pending> }
Promise { <pending> }
Promise { 'resolve' }
Promise { 'resolve' }
Promise { 'resolve' }
...
*/
Promise 의 then 과 catch
then
promise
가 종료가 되면 resolve
에 들어간 값을 받을 수 있습니다.
const promise = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve("resolve");
}, 3000);
});
promise.then(value => console.log(value));
// 3초 후에 결과가 출력
// resolve
catch
하지만 reject
된 경우에는 then
으로 받을 경우, 에러가 발생합니다.
const promise = new Promise((resolve, reject)=>{
setTimeout(()=>{
reject("reject");
}, 3000);
});
promise.then(value => console.log(value));
// UnhandledPromiseRejectionWarning: reject
이 때 catch
를 사용하여 에러를 잡아줄 수 있습니다.
const promise = new Promise((resolve, reject)=>{
setTimeout(()=>{
reject("reject");
}, 3000);
});
promise.then(result => console.log(result)).catch(error => console.log(error));
// 3초 후에 결과가 출력
// reject
catch
가 then
뒤에 연결되어있기 때문에 then 이 먼저 실행 후 catch
가 실행 된다고 생각할 수 있지만 그렇지 않습니다.
둘은 절대 같이 실행 될 수 없습니다. resolve
시에는 then
, reject
시에는 catch
가 실행되는 것이죠.
Chaining Promise
다시 promise 와 then 을 써보면 아래와 같습니다.
결과가 2가 나오는건 별로 어렵지 않습니다.
const promise = new Promise((resolve, reject)=>{
resolve(2);
});
promise.then(result => console.log(result));
// 2
하지만 상황에 따라 promise
를 여러 번 사용해야 하는 경우가 존재합니다. API 로 data 를 받아오기 위해서 promise
를 사용하고 받아온 데이터의 암호를 풀기 위해서 promise
를 한 번 더 사용할 수 있습니다.
이를 Chaining Promise
라고 합니다.
간단한 연산을 예시로 한 번 알아보도록 하겠습니다.
const promise = new Promise((resolve, reject)=>{
resolve(2);
});
promise.then(result => {
const output = result+1;
console.log(output);
return output;
})
.then(result => {
const output = result+1;
console.log(output);
return output;
})
// 3
// 4
chain 이 여러 개 연결 된 것 처럼 then
은 원하는 만큼 많이 연결할 수 있습니다.
const promise = new Promise((resolve, reject)=>{
resolve(2);
});
const plusOne = num => num + 1;
promise
.then(plusOne)
.then(plusOne)
.then(plusOne)
.then(plusOne)
.then(result => console.log(result));
// 6
그런데 중간의 chain 에서 에러가 발생하면 어떻게 될까요?
이를 해결하기 위해서 각 then
마다 catch
를 넣어줘야 할까요?
promise
.then(plusOne)
.then(plusOne)
.then(plusOne)
.then(()=> {
throw Error("error")
})
.then(result => console.log(result));
// UnhandledPromiseRejectionWarning: Error: error
다행히도 catch
는 한 번의 사용으로 모든 then
에 대해서 해결할 수 있습니다.
const promise = new Promise((resolve, reject)=>{
resolve(2);
});
const plusOne = num => num + 1;
promise
.then(plusOne)
.then(plusOne)
.then(plusOne)
.then(()=> {
throw Error("error")
})
.then(result => console.log(result))
.catch(error => console.log(error));
// Error: error
Promise all
all()
은 주어진 모든 Promise
를 실행한 후 진행되는 하나의 Promise 를 반환합니다.
예시를 통해 살펴보겠습니다.
3개의 promise
에 대해 이들을 array 로 all
에 넘겨주면 allPromise
는 3개의 promise
가 모두 끝날 때 까지 기다린 후에 이들의 결과값을 array로 반환하게 됩니다.
const p1 = new Promise(resolve => {
setTimeout(resolve, 2000, "First");
})
const p2 = new Promise(resolve => {
setTimeout(resolve, 1000, "Second");
})
const p3 = new Promise(resolve => {
setTimeout(resolve, 3000, "Third");
})
const allPromise = Promise.all([p1,p2,p3]);
allPromise.then(values => console.log(values));
// [ 'First', 'Second', 'Third' ]
이 중 하나의 promise
에서 에러가 발생한다면 어떻게 될까요?
allPromise
는 모든 promise
에 대해서 결과값을 받아와야 하는데 그러지 못하므로 에러가 발생하게 됩니다.
const p1 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, "First reject");
})
const p2 = new Promise(resolve => {
setTimeout(resolve, 1000, "Second");
})
const p3 = new Promise(resolve => {
setTimeout(resolve, 3000, "Third");
})
const allPromise = Promise.all([p1,p2,p3]);
allPromise.then(values => console.log(values));
// UnhandledPromiseRejectionWarning: First reject
이 경우 catch
는 각 promise
에 해줄 필요는 없고, allPromise
에 대해서만 해주면 됩니다.
const p1 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, "First reject");
})
const p2 = new Promise(resolve => {
setTimeout(resolve, 1000, "Second");
})
const p3 = new Promise(resolve => {
setTimeout(resolve, 3000, "Third");
})
const allPromise = Promise.all([p1,p2,p3]);
allPromise
.then(values => console.log(values))
.catch(error => console.log(error));
// First reject
Promise race
race()
는 주어진 Promise
중 가장 먼저 완료된 것의 결과값을 이행하거나 거부합니다.
위에서 살펴본 all
과 사용하는 방법은 같습니다.
p1
은reject
하고 있지만 결과는 Second 가 나옵니다.
p2
는 1초만에 끝나기 때문에 그 결과값을 받아 바로 출력해버리는 것이죠.
const p1 = new Promise((resolve, reject) => {
setTimeout(reject, 2000, "First reject");
})
const p2 = new Promise(resolve => {
setTimeout(resolve, 1000, "Second");
})
const p3 = new Promise(resolve => {
setTimeout(resolve, 3000, "Third");
})
const allPromise = Promise.race([p1,p2,p3]);
allPromise
.then(values => console.log(values))
.catch(error => console.log(error));
// Second
Promise finally
finally()
는 Promise
가 resolve
되던 reject
되던 상관없이 지정된 함수를 실행합니다.
이를 통해 promise
의 결과에 상관없이 동작을 해야할 때 유용하게 구현할 수 있습니다.
해당 예시가 중간에 에러가 발생하고 이를 catch
로 잡아주었어도 finally
는 실행되어 end 를 출력합니다.
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, "First");
});
p1
.then(result => console.log(result))
.finally(()=>console.log("end"));
// First
// end
Promise 활용
포스팅의 도입부에서 fetch
를 봤었습니다.
이도 해당 url 에 대해 promise
객체를 반환하기 때문에 promise
처럼 사용할 수 있습니다.
그러면 영화목록을 제공해주는 API 를 통해 데이터를 가져와보도록 하겠습니다.
fetch
를 사용하여 API endpoint 에 대해 response 를 확인해보면 아래와 같습니다.
fetch("https://yts.lt/api/v2/list_movies.json")
.then(response => console.log(response))
.catch(error => console.log(error));
// Response {type: "cors", url: "https://yts.lt/api/v2/list_movies.json", redirected: false, status: 200, ok: true, …}
이는 0과 1로 이루어진 Stream 이기 때문에 읽을 수 있는 정보로 바꾸기 위해서 JSON 으로 바꿔줘야 합니다.
하지만 예상 외의 문제가 발생합니다.
이는 response 가 promise
를 반환하는 함수를 가지고 있기 때문에 문제가 발생하는 것입니다.
fetch("https://yts.lt/api/v2/list_movies.json")
.then(response => {
console.log(response.json());
})
.catch(error => console.log(error));
// Promise {<pending>}
따라서 이를 위해 한 번 더 then
을 거치면 원하는 영화목록을 얻을 수 있습니다.
fetch("https://yts.lt/api/v2/list_movies.json")
.then(response => response.json())
.then(json => console.log(json))
.catch(error => console.log(error));
/*
{status: "ok", status_message: "Query was successful", data: {…}, @meta: {…}}
status: "ok"
status_message: "Query was successful"
data:
movie_count: 14457
limit: 20
page_number: 1
movies: Array(20)
0: {id: 14848, url: "https://yts.lt/movie/wolf-guy-1975", imdb_code: "tt0202114", title: "Wolf Guy", title_english: "Wolf Guy", …}
1: {id: 14847, url: "https://yts.lt/movie/the-sublet-2015", imdb_code: "tt4285960", title: "The Sublet", title_english: "The Sublet", …}
생략
19: {id: 14829, url: "https://yts.lt/movie/the-story-of-temple-drake-1933", imdb_code: "tt0024617", title: "The Story of Temple Drake", title_english: "The Story of Temple Drake", …}
length: 20
__proto__: Array(0)
__proto__: Object
@meta: {server_time: 1577980301, server_timezone: "CET", api_version: 2, execution_time: "0.01 ms"}
__proto__: Object
*/
'웹프로그래밍 > ES6' 카테고리의 다른 글
[ES6] Class의 상속, super, this (0) | 2020.01.04 |
---|---|
[ES6] Async and Await (0) | 2020.01.03 |
[ES6] Loop 에 대해서 알아보자 forEach, for of (0) | 2020.01.02 |
[ES6] Spread and Rest (0) | 2020.01.02 |
[ES6] Destructuring (0) | 2019.12.31 |