자바스크립트는 Single Thread 언어이다.
이벤트를 처리하는 Call Stack(함수 실행 호출이 쌓이는 곳. 호출이 끝나면 사라짐)이 하나밖에 없는 언어라는 뜻이다.
그래서 여러 이벤트를 처리할 때 동기적으로 처리하면, 모든 이벤트를 처리할 때까지 다른 이벤트나 업무를 수행하지 못하게 된다.
자바스크립트 구동순서 참고
따라서, 자바스크립트는 즉시 처리 불가 이벤트들을 Web API로 보내 콜스택이 비었을 때, 먼저 처리된 이벤트들을 줄세워 다시 이벤트 큐에 줄을 세운다.
Web API로 들어오는 순서보다는 어떤 이벤트가 먼저 처리되느냐가 중요.
만약 비동기 처리 이벤트들의 순서가 중요해지게 된다면 어떨까?
ex) 서버에 로그인 사용자 아이디를 요청하는 비동기 처리 후 사용자의 아이디를 이용해 프로필 사진 정보를 재요청해야하는 상황이라면?
순차적으로 진행되어야 하는 비동기를 처리하는 여러 방식有.
처리되어야 할 이벤트들을 순차적으로 콜백 함수로 넣어주는 방식, 일명 콜백 지옥
doItFirst(function(result) {
doItSecond(result, function(newResult) {
doItThird(newResult, function(finalResult) {
console.log('What is the last result : ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
함수의 최초 반환값 result를 받아, 다음 비동기 처리를 해야하는 경우에 함수를 콜백으로 받아 비동기 처리 가능
많이 사용된다고 하나, 고전이고 지옥이라 불릴 만큼 치명적인 단점O
Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과값을 나타냅니다.
출처 MDN
Promise를 사용하여 비동기 작업이(성공 혹은 실패) 완료된 후의 결과값을 받을 수 있다.
결과값을 돌려받을 수 있기 때문에 이후 처리를 컨트롤 할 수 있게 된다.
Promise의 상태값
Promise 객체는 new 키워드로 생성할 수 있으며 총 4개의 상태값을 가진다.
Pending: 아직 결과값이 반환되지 않은 진행중인 상태
fulfilled: 성공
Rejected: 실패
Settled: 결과값이 성공 혹은 실패로 반환된 상태
상태값은 크게 Pending과 Settled로 나눌 수 있으며, Settled는 다시 fulfilled와 Rejected로 나누어진다.
한 번 Settled된 값은 재실행 할 수 없다.
Promise는 함수를 인자로 받으며 인자로 들어온 함수는 다시 resolve와 rejected 2개의 함수를 인자로 받게 된다.
resolve는 비동기 처리 성공 시 호출, rejec는 비동기 처리 실패시 호출된다.
const promise = new Promise(function(res, rej) {
setTimeout(function() {
res(111);
}, 1000);
});
// 화살표 함수로 작성해도 동일
const promise = new Promise((res, rej) => {
setTimeout(()) => {
res(111);
}, 1000);
});
인스턴스 호출 시에는 대표적으로 then
과 catch
메소드를 사용한다.
resolve시 then
으로
resolve되는 값은 then 메소드의 인자로 넘어간다.
const promise = new Promise((res, rej) => {
setTimeout(() => {
res(111);
}, 1000);
});
promise.then((res) => console.log(res));
// 출력값
111
reject시 catch
로
resolve와 반대로 reject되는 값은 catch메소드의 인자로 넘겨 에러 핸들링을 할 수 있다.
const promise = new Promise((res, rej) => {
setTimeout(() => {
rej('error!');
}, 1000);
});
promise
.then((res) => console.log(res))
.catch((err) => console.log('error : ' + err));
// catch 메소드에 잡혀서 console.log(err)에서 출력된 값
error!
여기서 또 중요한 점은 then
메소드는 다시 Promise
를 반환한다는 것!!
Promise
객체를 반환한다는 것은 then
, catch
메소드를 사용할 수 있다는 것을 뜻하며, 이를 통해 then
메소드로 연속적인 Promise chaining
이 가능하다는 것을 의미한다.
.catch() 이후에도 chaining가능
new Promise((res, rej) => {
console.log('Initial');
res(); // resolve된 후 then 실행
})
.then(() => {
throw new Error('Something failed');
console.log('Do this'); // error 발생으로 실행되지 않는다.
})
.catch((err) => {
console.log(err); // throw new Error의 인자값이 넘어온다.
})
.then(() => {
console.log('Do this, whatever happened before');
// catch 구문 이후 chaining
});
// 출력값
Initial
Something failed // ERROR
Do this, whatever happened before // catch 메소드 이후 then 메소드 실행
Promise.all
Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행된 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환.
주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부
출처 MDN
Promise.all
은 Promise
인스턴스들이 담긴 배열을 인자로 받아 사용하는데, 배열의 모든 요소가 Promise
인스턴스일 필요는 없다.
const promise1 = Promise.resolve(3);
const promise2 = 42; // 프로미스가 아니어도 괜찮아요~
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(values => {
console.log(values); // Array [3, 42, 'foo']
});
// resolve되는 값들을 destructuring 할 수 있다.
Promise.all([promise1, promise2, promise3])
.then(([one, two, three]) => {
console.log(one); // 3
console.log(two); // 42
console.log(three); // 'foo'
});
Promise
인스턴스 외 사용 예시
const emptyPromiseAll = Promise.all([]); // 빈 배열을 동기적 실행
// 프로미스가 아닌 값은 무시하지만 비동기적으로 실행됨
const promiseA = Promise.all([1337, "hi"]);
// 위와 동일
const promiseB = Promise.all([1, 2, 3, Promise.resolve(444)]);
p; // [] 동기적 실행히라 then 메소드 사용하지 않아도 된다.
p2.then(res => console.log(res)); // [1337, 'hi']
p3.then(res => console.log(res)); // [1, 2, 3, 444]
MDN에 매우 다양한 예시가 있는데 다양한 조합으로 동기, 비동기 실행 차이와 비동기 실행시 pending과 settled되는 경우들을 직접 개발자 도구창에 찍어보는 것이 좋다.
async function 선언 AsyncFunction객체 반환하는 하나의 비동기 함수를 정의.
비동기 함수는 이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise를 사용하여 결과를 반환합니다.
비동기 함수를 사용하는 코드의 구문과 구조는, 표준 동기 함수를 사용하는 것과 많이 비슷합니다.
출처 MDN
ES2017에 등장한 async / await
구문은 Promise
를 기반으로 사용되는데 위의 MDN의 설명과 같이 비동기 코드를 좀 더 동기적인 코드처럼 작성할 수 있게끔 한다.
이번 프로젝트에서는 .then, .catch구문만 사용해봤지만 async / await를 사용해봐야겠다.
async / await
는 Promise
를 대체하는 것이 아니라는 것을 유념!!
Promise
를 사용하지만 then
, catch
메소드를 사용하여 컨트롤 하는 것이 아닌 동기적 코드처럼 반환값을 변수에 할당하여 작성할 수 있게끔 도와주는 문법.
await 키워드는 async 함수에서만 사용 가능하며 async 함수가 아닌 곳에서 사용하면 아래와 같이 SyntaxError가 발생
error handling
try.. catch..
구문을 사용할 수 있으며 Promise chaining
에서 연결된 then
메소드 중 하나라도 에러가 발생할 시 catch
메소드로 잡는 것처럼 비동기 처리 중 에러 발생 시 catch
블럭에서 잡히게 된다.
function promise() {
return new Promise(resolve => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
try {
console.log(1);
const result = await promise(); // Promise가 settled될 때까지 기다린 후 resolve된 값을 할당한다.
console.log(result);
console.log(2);
} catch(err) {
console.error(err); // error 발생 시 catch 블락에서 잡히도록 handling
}
}
asyncCall();
// 출력 값
1 // asyncCall 호출
resolved // resolve 함수 호출
2 // await 후 다음 코드 실행
아래의 경우는?
console.log(0);
function promise() {
console.log(1);
return new Promise(resolve => {
setTimeout(() => {
console.log(2);
resolve('resolved');
}, 2000);
});
}
console.log(3);
async function asyncCall() {
try {
console.log(4);
const result = await promise(); // Promise가 settled될 때까지 기다린 후 resolve된 값을 할당한다.
console.log(result);
console.log(5);
} catch(err) {
console.error(err); // error 발생 시 catch 블락에서 잡히도록 handling
}
}
console.log(6);
asyncCall();
// 출력 값
0
3
6
4 // asyncCall 호출
1 // promise 함수 호출
2 // 2초 후 setTimeout 콜백 함수 호출
resolved // resolve함수 호출
5 // await 후 다음 코드 실행
두 예제에서 눈여겨볼 점은 await
로 호출한 함수 출력값 result
를 할당한 후의 console.log들이다.
비동기 상황에서는 어떤 이벤트가 먼저 완료될 지 순서가 불명확하나,
async await
사용 시 먼저 완료되어야 할 이벤트 순서대로, 동기적으로 실행되는 코드처럼 작성할 수 있다.
실제 코드 작성 시에는 axios나 fetch를 통해 서버에 요청을 하면 자동으로 Promise
를 반환해주기 때문에 Promise
객체를 직접 생성하는 경우보다 Promise
로 반환되는 객체들을 async await
를 사용하여 비동기 처리하는 경우가 대부분.
하지만, Promise의 구동 방식을 알고 있어야 사용할 때도 그 흐름을 알 수 있다.
Finally
, async / await 사용 시도해보자