본문 바로가기
Language/Javascript

Promise (2) 프로미스 체이닝, Promise.all, Async/Await

by joooing 2021. 2. 2.
반응형

앞선 글에서 살펴봤듯이 Promise는 then, catch, finally 등의 후속 처리 메서드를 사용함으로써 콜벡헬 문제도 해결할 수 있다. 이런 후속 처리 메서드들이 연속적으로 호출되는 '프로미스 체이닝', 여러개의 비동기작업들을 병렬로 처리해버리는 Promise.all, 그리고 async/await 키워드를 통해 후속처리 메서드를 쓰지 않고 비동기를 구현하는 방법까지 살펴보도록 하자.

 

Promise Chaining (프로미스 체이닝)


후속처리 메서드들은 언제나 Promise를 반환하기 때문에 연속 호출이 가능하다. 이런 연속 호출 과정을 '프로미스 체이닝'이라고 한다. 아래 예시처럼 사용자 정보를 얻어 파싱과 인증 작업을 거치는 과정에 사용될 수 있다. 이렇게 거쳐야 하는 여러 과정을 then 메서드를 통해 연결해 처리할 수 있다. 예시에는 두 단계밖에 없지만 쭉 이어서 .then().then().then()... 처럼 훨씬 많은 단계를 추가할 수도 있다. 에러는 catch를 통해 처리가 가능하다.

 

promiseGet(userData){
  .then(파싱)
  .then(인증)
  .catch(err)
}

 

var userInfo = {
  Id: 'jooing',
  Password: '1234'
};

function 파싱() {
  return new Promise({...});
}
function 인증() {
  return new Promise({...});
}

 

promise.all


promise.all은 여러 개의 비동기 처리를 모두 병렬 처리하고자 할 때 사용한다. 인자로는 [배열]을 전달받는데, 이 배열에 있는 모든 프로미스가 성공일 경우에만(fulfilled 상태) 모든 처리 결과를 배열에 저장해 새로운 프로미스를 반환해준다.

 

const req1 = () => new Promise(resolve => setTimeout(() => resolve(10), 1000));
const req2 = () => new Promise(resolve => setTimeout(() => resolve(20), 2000));
const req3 = () => new Promise(resolve => setTimeout(() => resolve(30), 3000));

 

기존처럼 단순히 프로미스 체이닝 방식을 사용했을 때부터 살펴보자. 이 때는 순차적으로 코드들이 실행되기 때문에 총 6초의 시간이 걸리게 된다. 같은 코드가 여러번 반복되는 것도 볼 수 있다.

 

// 기존
const res = [];
req1()
  .then(data => {
    res.push(data);
    return req2();
  })
  .then(data => {
    res.push(data);
    return req3();
  })
  .then(data => {
    res.push(data);
   console.log(res);  // [10, 20, 30]
  })
  .catch(console.error);

 

이제 promise.all을 썼을 때를 살펴보자. promise.all은 비동기 처리들을 '병렬'로 처리해준다고 언급했다. 그렇다면 아래 코드가 실행되는데 몇 초의 시간이 걸릴까? 인자로 받는 함수들 중 가장 오래걸리는 시간인 3초가 전체 처리 시간이 될 것이다. 코드 또한 훨씬 간결해진 것을 볼 수 있다.

 

// promise.all
Promise.all([req1(), req2(), req3()])
  .then(console.log)  // [10, 20, 30]
  .catch(console.error);

 

추가로, promise.all은 인자로 받는 배열에 포함되는 프로미스들 중 하나라도 rejected 상태가 되면 나머지 프로미스의 상태에 상관없이 즉시 종료된다. 아래 예시에서는 가장 먼저 1초만에 실패 처리되는 에러3이 반환된 이후로는 더 이상 처리를 진행하지 않고 종료되어버린다.

 

Promise.all([
	new Promise((_, reject) => setTimeout(() => reject(new Error('에러1')), 3000),
	new Promise((_, reject) => setTimeout(() => reject(new Error('에러2')), 2000),
	new Promise((_, reject) => setTimeout(() => reject(new Error('에러3')), 1000),
])
	.then(console.log)
	.catch(console.log);  // Error: 에러3

 

async / await


async와 await은 ES8 에서 도입된 키워드들인데, 후속처리 메서드 없이 동기처리를 하듯이 구현이 가능하다. 훨씬 간단하고 가독성 좋게 표현할 수 있는데, 일단 기본적인 문법부터 살펴보자.

 

기본문법

 

// function ver
async function 함수명() {
  await 비동기메서드();
}

// arrow function ver
async () => {
  await 비동기메서드();
}

 

1) async 키워드 : 함수명 앞에!

언제나 프로미스를 반환한다. 명시적으로 표기하지 않아도 암묵적으로 반환값을 resolve하는 프로미스를 반환한다.

 

2) await 키워드 : 함수 내부, 비동기 처리하는 메서드 앞에!

await는 프로미스가 settled 상태가 될 때까지 대기했다가, 프로미스가 resolve한 처리 결과를 반환한다. 모든 프로미스에 await 키워드를 씀으로써 비동기 처리의 처리 순서를 보장할 수 있다.

 

보통 await의 대상이 되는 메서드는 프로미스를 반환하는 API 호출 함수들이다. 또 하나 주의할 점은 비동기 처리 메서드가 반드시 프로미스 객체를 반환해야만 await가 의도한 대로 동작한다는 점이다.

 

예시

 

function fetchItems() {
  return new Promise((resolve, reject) => {
    let nums = [10,20,30];
    resolve(nums);
  });
}

async function logItems() {
  let result = await fetchItems();
  console.log(result); // [10,20,30]
}

 

 

 

반응형

댓글