Vana Blog

Node.js Design Pattern - ES6이후 비동기식 프로그램의 제어 흐름 패턴(1)

May 04, 2019

프로미스(Promise)

ES6~, Node4~ 부터 Node.js에서 기본적으로 사용할 수 있다.

용어 설명
대기중(Pending) 아직 비동기 작업이 완료되지 않음.
이행됨(Fulfilled) 성공적으로 끝난 상태.
거부됨(Rejected) 작업이 실패하여 종료됨.
처리됨(Settled) Promise가 이행되거나 거부됨.

결과를 받으려면 then()메소드를 사용한다.

promise.then([onFulfilled], [onRejected])
 
asyncOperation(arg)
.then(result => {
  // 결과 처리
}, err => {
  // 에러 처리
});
 
asyncOperation(arg)
  .then(result1 => {
  // 다른 Promise를 반환
  return asyncOperation(arg2);
}).then(result2 => {
  // 값을 반환
  return 'done';
}).then(undefined, err => {
  // 체인의 모든 에러를 여기서 처리함
});

then()메소드는 동기식으로 다른 Promise를 반환한다. 마지막 then()메소드 실행 후 값을 가지고 동기적으로 Promise를 해결(resolve)할지라도 onFulfilled()와 onRejected() 함수에 대한 비동기적인 호출을 보장한다. onFulfilled() 또는 onRejected() 핸들러에서 예외(throw()를 사용한 예외)가 발생한 경우 then() 메소드를 통해 반환된 Promise는 발생한 예외를 이유로하여 자동으로 거부된다.

Promise API

생성자(new Promise(function (resolve, reject){}))
용어 설명
resolve(obj) 값이 then 가능한(즉, then 메소드를 가진 객체인)경우 반환된 Promise는 then() 메소드를 처리하고 마지막 상태를 취함. 그렇지 않은 경우 반환된 Promise는 주어진 값으로 이행한다.
reject(err) err를 이유로 Promise를 거부한다. err은 Error의 인스턴스다.
Promise 객체의 정적 메소드들
용어 설명
Promise.resolve(obj) thenable이나 값으로 새로운 Promise를 생성.
Promise.reject(err) 주어진 이유로 거부되는 Promise 객체를 만듬.
Promise.all(iterable) 반복 가능한 객체의 모든 항목들이 이행되고 나면 모든 이행 값들을 가지고 이행하는 Promise를 생성하는데, 하나의 항목이라도 거부될 경우 첫 번째 거절 이유(reject reason)를 가지고 거절된다. 반복 가능한 객체 내 항목들은 Promise, thenable 또는 그냥 값일 수 있다.
Promise.race(iterable) 반복 가능한 객체 내에 있는 Promise들 중 가장 먼저 이행되거나 거절된 결과를 가지고 이행되거나 거부되는 Promise를 반환.
Promise 인스턴스의 메소드들
용어 설명
Promise.then(onFulfilled, onRejected) Promise의 필수 메소드
Promise.catch(onRejected) Promise.then(undefined, onRejected)와 동일한 동작을 하는 간편 버전.

// 순차 반복
function spiderLinks(currentUrl, body, nesting) {
let promise = Promise.resolve();
if(nesting === 0) {
return promise;
}
const links = utilities.getPageLinks(currentUrl, body);
links.forEach(link => {
promise = promise.then(() => spiderLinks(link, nesting - 1));
});
return promise;
}
// 순차 반복 - 패턴
let tasks = [/* ... */]
let promise = tasks.reduce((prev, task) => {
return prev.then(() => {
return task();
});
}, Promise.resolve());
promise.then(() => {
// 모든 작업들이 완료됨
});
// 병렬 실행
function spiderLinks(currentUrl, body, nesting) {
if(nesting === 0) {
return Promise.resolve();
}
const links = utilities.getPageLinks(currentUrl, body);
const promises = links.map(link => spiderLinks(link, nesting - 1));
return Promise.all(promises);
}
// 제한된 병렬 실행
// TaskQueue Class의 next() 함수 수정
next() {
while (this.running < this.concurrency && this.queueMicrotask.length) {
const task = this.queue.shift();
task().then(() => {
this.running--;
this.next();
});
this.running++;
}
}
// 수정된 spiderLinks
function spiderLInks(currentUrl, body, nesting) {
if(nesting === 0) {
return Promise.resolve();
}
const links = utilities.getPageLinks(currentUrl, body);
// 처리할 작업이 없는 경우, 다음 번에 만드는 Promise는 결정(settle)될 수 없기 때문에 다음과 같은 과정이 필요하다.
if(links.length === 0) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
let completed = 0;
let errored = false;
links.forEach(link => {
let task = () => {
return spider(link, nesting - 1)
.then(() => {
if(++completed === links.length) {
resolve();
}
}).catch(() => {
if(!errored) {
errored = true;
reject();
}
});
};
downloadQueue.pushTask(task);
})
})
}
view raw p_spider.js hosted with ❤ by GitHub

공개 API로 콜백과 Promise 노출하기

module.exports = function asyncDivision(dividend, divisor, cb) {
  return new Promise((resolve, reject) => {
    process.nextTick(() => {
      const result = dividend / divisor;
      if(isNaN(result) || !Number.isFinite(result)) {
        const error = new Error('Invalid operands');
        if(cb) {
          cb(error);
        }
        return reject(error);
      }
      if(cb) {
        cb(null, result);
        resolve(result);
      }
    });
  });
}
 
// 콜백을 사용한 방법
asyncDivision(10, 2, (error, result) => {
  if(error) {
    return console.error(error);
  }
  console.log(result);
});
 
// Promise를 사용한 방법
asyncDivision(22, 11)
.then(result => console.log(result))
.catch(error => console.error(error));

Vana Yun

Written by Vana Yun