Promise
21 Dec 2020 -
4 minute read
Synchronous 동기
- 요청을 보낸 후 해당 요청의 응답을 받아야 다음 동작을 실행 (은행)
Asynchronous 비동기
- 요청을 보낸 후 응답과 관계없이 다음 동작을 실행 (카페)
Intro
자바스크립트는 동기적이며, single threaded이다
- 자바스크립트는 single threaded(프로세스 내에서 단일 실행, 콜스택이 하나)로서, 여러 개의 스크립트가 동시에 실행될 수 없다. => 한 번에 하나의 명령만 처리
- 브라우저에서 자바스크립트는 사용자 작업의 처리, 스타일 업데이트 등의 다른 작업과 같은 대기열에 있으며, 브라우저는 어떠한 작업이 완료되기 전에 다음 작업을 수행할 수 없다. => blocking이 일어난다
- 자바스크립트는 동기적이며 각 코드가 순서대로 실행된다.
자바스크립트의 비동기 프로그래밍
- 자바스크립트는 비동기 프로그래밍을 사용하여 요청을 보낸 후 응답에 관계 없이 바로 다음 동작을 이어서 실행한다.
- 자바스크립트의 비동기 함수는 특정 코드의 실행이 끝나기 전에 계속 다음 코드를 실행하기 때문에, 실행이 서로 연관되어 있는 코드가 있을 경우 에러가 발생할 수 있다.
- 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용하는데, 서버에 데이터 요청 후 화면에 표시될 때 데이터를 다 받아오지 않은 상태에서는 오류가 발생하거나 빈 화면이 표시되게 된다. 때문에 데이터를 받아오는 작업이 모두 완료된 후 화면에 표시하는 함수가 실행되어야 하는데, 이러한 비동기 처리 방식의 문제를 해결하기 위해 콜백 함수를 사용한다.
- 콜백 함수: 자바스크립트에서 비동기를 관리하는 방법 중 하나
- 콜백 함수를 parameter로 받게 되면 제어권을 콜백 함수에게 넘겨주게 된다.
- 즉, 콜백 함수는 비동기 함수가 완료된 후 실행되어야 할 코드를 담고 있다가 함수 내에서 정해진 방식에 따라 콜백 함수를 호출할 때 실행된다.
- 비동기를 순서대로 처리하기 위해 콜백 함수를 중첩하여 사용하다 보면 depth가 깊어져 콜백 지옥이 생길 수 있다.
// 콜백 지옥
step1(function (value1) {
step2(function (value2) {
step3(function (value3) {
step4(function (value4) {
step5(function (value5) {
step6(function (value6) {
// Do something with value6
});
});
});
});
});
});
- 이러한 콜백 지옥을 해결하기 위해 Promise, AsyncAwait를 사용한다.
- 비동기적인 함수를 바로 실행하는 대신 동기 함수처럼 값을 반환하도록 한다.
Promise
- 비동기 처리에 사용되는 객체
- 비동기 작업이 완료된 후의 값을 객체로 받아 이후 처리를 관리한다.
- 최종 결과가 아니라 프로미스를 반환하여 미래의 어떤 시점에 결과를 제공한다.
- new Promise를 통해 인스턴스를 생성한다.
- 매개변수로 executor(실행 함수)를 전달받고, 이 함수가 프로미스 구현에 의해 다시 resolve/reject 함수를 인자로 받는다.
Promise의 상태
- 대기(pending): 이행하거나 거부되지 않은 초기 상태
- 이행(fulfilled): 비동기 작업이 성공적으로 완료되어 resolve가 호출된 상태
- 거부(rejected): 비동기 작업 중 오류가 발생하여 reject를 호출한 상태
Promise의 메소드
- resolve(): 비동기 작업 성공 시 주어진 결과값을 담은 Promise 객체를 반환 => 이행 상태
- reject(): 비동기 작업 실패 시 에러 값(오류 원인)을 담은 Promise 객체를 반환 => 거부 상태
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('do');
}, 300);
});
promise1.then((value) => console.log(value)); // 결과값: 'do'
console.log(promise1) // 결과값: [object Promise]
Promise 프로토타입 메소드
- then(): resolve를 통해 받은 값을 전달받아 Promise 객체를 반환
- promise가 이행 상태일 때를 위한 콜백함수와 거부 상태일 때를 위한 콜백함수를 인자로 받으며, 인자는 생략 가능하고 생략 시 추가 핸들러가 없는 Promise를 생성하게 된다.
- 이행 상태 콜백함수(onFulfilled)는 이행 값을 인자로 받고, 거부 상태 콜백함수(onRejected)는 거부 이유를 인자로 받는다.
// 방법1
promise.then(onFulfilled, onRejected);
// 방법2
promise.then(function(value) {
// 이행
}, function(reason) {
// 거부
});
- catch(): reject를 통해 받은 값을 전달받아 Promise 객체를 반환
- catch(onRejected) === then(null, onRejected)
// 방법1
promise.catch(onRejected);
// 방법2
promise.catch(function(reason) {
// 거부
});
Promise Chaining
- 순차적으로 각각의 작업이 이전 단계 비동기 작업이 성공하고 나서 그 결과값을 이용하여 다음 비동기 작업을 실행할 때 chain을 사용할 수 있다.
- then() 메소드는 처음 promise와는 다른 새로운 promise를 반환한다.
const promise1 = doSomething();
const promise2 = promise1.then(successCallback, failureCallback);
//
const promise2 = doSomething().then(successCallback, failureCallback);
- 여러개의 콜백을 추가하여 각각의 콜백이 주어진 순서대로 실행되게 된다.
doSomething()
.then(data1 => doSomethingElse(data1))
.then(data2 => doThirdThing(data2))
.then(data3 => {
console.log(data3))
})
.catch(failureCallback);
Async/await
- 비동기를 관리하는 또 다른 방법으로, AsyncFunction 객체를 반환하는 비동기 함수(async function)를 정의한다.
- async 키워드를 통해 비동기 함수를 선언한다. async 함수는 항상 promise를 반환한다.
- Promise를 가독성 좋게 사용하는 방법이다.
- await 키워드는 함수의 실행을 일시 중지하고 전달된 프로미스가 처리(settled)될 때까지 기다린 다음 async 함수의 실행을 다시 시작하고 완료 후 값을 반환하게 한다.
- await 키워드를 포함하는 최상위 코드는 동기적으로 실행되고, aync 함수는 비동기적으로 완료된다.
- await 키워드는 async 함수 내에서만 유효하며, await 키워드가 없는 async 함수는 동기적으로 실행된다.
Error handling
- try…catch문을 사용한다.
- error가 발생할 경우 catch block 실행
const fetchAsync = async() => {
try {
const response = await fetch(URL);
const user = await response.json();
} catch (err) {
alert(err);
}
}
Promise.all
- 여러 프로미스를 한번에 배열로 받아 모든 프로미스가 성공적으로 완료된 후에 Promise를 반환한다.
- 프로미스 중 하나라도 거부되는 경우 거부된 프로미스의 에러 이유를 그대로 사용하여 거부한다.
Promise.all([promise1, promise2, promise3]).then((values => {
console.log(values);
}];
- async/await와 Promise.all
- Promise.all을 사용하면 병렬로 비동기 함수를 실행시킬 수 있다.
- 더 빠른 처리가 가능하고, 중간에 비동기 처리가 실패했을 시에도 즉시 동작을 종료하고 에러를 반환한다.
- 순서대로 실행되지만 앞의 함수가 완료되는 것을 기다리지 않고 다음 함수가 실행되고, 마지막으로 완료되는 함수를 기다렸다가 값을 반환한다.
// async/await
await display('data1', 3000)
await display('data2', 2000)
await display('data3', 1000)
// 결과: (3000ms) -> data1 -> (2000ms) -> data2 -> (1000ms) -> data1
// 소요시간: 약 6000ms
// Promise.all 병렬 실행
await Promise.all([
display('data1', 3000),
display('data2', 2000),
display('data3', 1000)
]);
// 결과: data1, data2, data1
// 소요시간: 약 3000ms 안에 모두 실행