자바스크립트의 약속(Promise): 3부 관심사의 분리

지난 2부에서는 JSON 파일을 읽어 domain key 값을 다시 파일에 쓰는 코드를 sync, callback, promise, async function 총 4가지 버전으로 작성해 보았습니다.

이 과정에서 알게 된 사실은 callback을 제외한 sync, promise, async function은 코드의 구조가 거의 똑같다는 사실입니다. 그래서 promise는 단순히 스타일이 다른 비동기 프로그래밍 방식이 아니라 코드를 좀 더 sync하게 작성하기 위한 방법이고, async function으로 넘어가기 위한 중간 단계라고 설명했습니다.

2부에서 살펴본 promise 예제를 다시 한 번 살펴보겠습니다.

var fs = require('fs');
var Promise = require('promise');
 
var readFile= Promise.denodeify(fs.readFile);
var writeFile= Promise.denodeify(fs.writeFile);
 
readFile('config.json')
  .then(function (text) {
    try {
      var obj = JSON.parse(text);
      writeFile('domain.txt', obj.domain).then(function () {
        console.log("Done");
      }).catch(function (reason) {
        console.error('Error while writing domain text file');
      });
    } catch (e) {
      console.error('Invalid JSON in file');
    }
  }).catch(function (reason) {
    console.error('Error while reading config file');
  });

이 코드는 sync 코드를 그대로 옮겼기 때문에 try/catch 블록과 catch() 함수가 같이 사용되어 조금 복잡해 보입니다. 코드를 좀 더 간결하게 만들기 위해 다음과 같이 에러 처리를 한 곳으로 모으고, then()을 중첩해서 부르는 대신 chaining하도록 바꾸겠습니다.

var fs = require('fs');
var Promise = require('promise');

var readFile= Promise.denodeify(fs.readFile);
var writeFile= Promise.denodeify(fs.writeFile);

readFile('config.json')
  .then(function (text) {
    var obj = JSON.parse(text);
    return writeFile('domain.txt', obj.domain);
  }).then(function () {
    console.log("Done");
  }).catch(function (reason) {
    if (reason.code === 'EACCES')
      console.error('Error while writing domain text file');
    else if (reason.code === 'ENOENT')
      console.error('Error while reading config file');
    else
      console.error('Invalid JSON in file');
  });

이 코드를 다시 sync 코드로 바꾸면 다음과 같습니다.

var fs = require('fs');

try {
  var text = fs.readFileSync('config.json');
  var obj = JSON.parse(text);
  fs.writeFileSync('domain.txt', obj.domain);
  console.log("Done");
} catch (reason) {
  if (reason.code === 'EACCES')
    console.error('Error while writing domain text file');
  else if (reason.code === 'ENOENT')
    console.error('Error while reading config file');
  else
    console.error('Invalid JSON in file');
}

이 두 코드를 비교하면 then의 의미가 좀 더 명확해집니다. sync 코드에서는 암묵적으로 코드가 한줄씩 실행된다고 가정하고, 다음줄을 실행할 때 이전 계산값이 이미 존재한다고 가정합니다. async 코드에서는 이런 보장이 없기 때문에 then()을 명시적으로 사용해 이전 줄의 실행 결과가 다음 줄을 실행할 때 필요하다고 표시합니다.

sync와 async의 차이점은 ‘a’가 있냐 없냐이고, 사실 종이 한 장 차이입니다. 코드의 로직은 전혀 차이가 없고, 단순히 동기냐 비동기냐의 차이만 있기 때문에 실제 코드도 거의 차이가 없어야 당연합니다. 하지만 node.js의 콜백 스타일은 ‘비동기’라는 단 하나의 걱정거리 때문에 코드 스타일이 완전히 달라졌기 때문에 좋지 않은 프로그래밍 스타일이라고 이야기할 수 있습니다.

then()함수의 의미는 동기/비동기의 차이를 하나의 함수로 캡슐화(encapsulation)했다는데 있습니다. promise를 이용해 작성한 코드는 then()의 구현체만 바꾸면 코드 변경 없이 같은 코드를 비동기가 아닌 동기로 구동할 수도 있습니다. 즉, 관심사의 분리(Separation of Concerns)라는 측면에서 promise는 “비동기 프로그래밍”이라는 관심사를 분리해냈다고 이해할 수도 있습니다.

Advertisements