Skip to content

7.4 백엔드와 비동기

Cojamm edited this page Nov 19, 2017 · 2 revisions

7.4.1 백엔드에서 만날 다양한 비동기 상황

백엔드에서는 비동기 상황을 더 자주 만나게 된다. 특히 Node.js를 이용하면 더욱 그렇다. 비동기 상황을 계속 마주하다 보면 콜백 지옥을 경험해 보게 된다. 콜백 지옥하면 가장 먼저 떠오르는 것은 Promise이고, 콜백 지옥을 벗어나는 것만이 목적이라면 Promise만으로도 충분하다(콜백 지옥이 비동기 지옥의 전부는 아니지만). 어쨌든 Promise는 한번 안쪽으로 파고든 들여 쓰기를 다시 바깥으로 꺼내는 데 용이하다. 안쪽으로 깊이 들어갔다고 해도 다시 바깥으로 쉽게 꺼낼 수 있다.

Promise가 자바스크립트의 콜백 지옥을 해결했던 다른 해법들과 가장 다른 점은 결과가 나오기로 약속된 Promise 객체를 값으로 다룰 수 있다는 점이다. Promise 객체는 일급 객체이므로 if else for 등으로도 어느 정도 다룰 수 있고, 변수에 담거나 함수의 인자로 사용할 수 있으며, 원하는 시점에 .then()과 함수 전달을 통해 결과값을 꺼내볼 수도 있다. Promise 객체를 값으로 다룰 수 있다는 점은 비동기 상황을 좀 더 편하게 제어할 수 있게 해준다. 이를테면 재귀 등으로 코딩해야 하는 상황 중 몇 가지 경우는 forPromise.all(); 등의 조합 정도로 대체할 수 있으며, if (bool) return new Promise(); else return new Promise(); 처럼 if else와 조합하는 일도 어느 정도 가능하다.

Promise는 비동기를 제어하는 다양한 동기적 패턴을 만들 수 있다는 가능성을 보여줬다. 앞서 말한 Promise.all([])을 이용하면 Promise 객체들을 배열로 넘겨 약속된 모든 결과가 완성된 시점을 잡을 수 있고, 그 결과들을 한번에 꺼내볼 수 있다. Promise.all의 사례는 Promise 객체 혹은 Promise 객체가 리턴될 함수들을 배열 등에 담거나 또 다른 함수에게 전달하거나 하는 식으로 다룰 수 있음을 보여 준다.

그렇지만 new Promise()Promise.all([])은 비동기 상황에서 필요한 수많은 로직 중 일부일 뿐이다. Promise.all의 경우 배열에 담긴 모든 Promise 객체의 실행 순서를 보장하지 않는다. 문제라는 얘기가 아니라 Promise.all의 로직이 동시에 출발한다는 얘기다. 정확히 말하면 Promise.all()에 담기기 전에 이미 시작된다. 그런데 실제로 우리가 만들어야 할 프로그램에서는 배열에 담긴 비동기 상황들이 앞에서부터 순서대로 하나씩 수행되어야 할 때도 있고, 하나씩 수행해 나가다가 특정 조건에 맞는 결과를 만나면 나머지는 수행하지 않아도 될 때도 있고, 수행하지 않아야만 할 때도, 다시 처음부터 시작해야 할 때도 있다. 이런 다양한 상황들을 Promise만으로는 완벽히 제어할 수 없다.

그런데 값으로 선언될 수 있고, 값으로 다뤄질 수 있으며, 인자로 사용할 수 있고, 배열 등에 담길 수 있다는 이야기는 어디서 많이 들어 본 이야기 아닌가? 그렇다. 함수 얘기다. 자바스크립트에서는 함수도 일급 객체이므로 Promise 객체가 없이도 함수를 값으로 다룰 수 있다. 결국 비동기 제어의 핵심은 함수 실행을 내가 원하는 대로 일렬로 나열하는 것이고, 이것은 함수만으로도 충분히 가능하며 어쩌면 더 쉽다. 더 쉽다는 말이 Promise의 이점을 부정하는 이야기는 아니다.

‘원하는 대로 일렬로 나열한다’는 것은 결국 ‘원하는 로직’을 만든다는 것이다. 그리고 함수형 프로그래밍은 원하는 로직을 함수로 구현하고, 구현된 함수들과 새로운 함수를 조합해가며 프로그래밍하는 것이다. 다양한 비동기 상황에 맞는 함수들을 준비해 두면, 나의 상황에 맞는 함수들을 골라 조합하는 식으로 비동기 상황을 매우 효율적으로 제어할 수 있다. 오류 없이 잘 동작하는 함수를 만들어두고 그 로직을 재사용하는 방식은 오류 발생률을 줄이며 문제의 난이도를 줄이고 개발자의 생산성을 높여 준다. 로직에 대한 부분은 다시 검증할 필요가 없고 그 부분만큼은 테스트 케이스를 작성할 필요도 없다. 내가 구현한 보조 함수나 주변 함수들에 대해서만 잘 검증하면 된다.

[코드 7-29] 넘겨준 결과를 그대로 1초 후에 돌려주는 함수
var delay = function(result) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(result);
    }, 1000);
  });
};

코드 7-30의 delay는 다양한 비동기 상황을 테스트하기 위해 만든 함수다. 넘겨준 결과를 그대로 1초 후에 돌려준다.

[코드 7-30] delay 사용해보기
delay(5).then(console.log); // 1초 후 5를 로그에 남김
delay(10 === 10).then(console.log); // 1초 후 true를 로그에 남김

미리 구현해 둔 고차 함수들을 이용해 다양한 비동기 상황을 제어하는 코드들을 확인해 보자.

7.4.2 순서대로 하나씩 실행하기

_.each는 순서대로 하나씩 실행하는 함수다. Partial.js의 주요 고차 함수들은 하나의 함수가 동기와 비동기를 모두 지원한다.

[코드 7-31] _.each
var list = _.each([1, 2, 3], function(v) {
  console.log(v);
});
console.log(list); // 즉시 결과 리턴
// 1
// 2
// 3
// [1, 2, 3]

var list2 = _.each([1, 2, 3], function(v) {
  return delay(v).then(console.log);
});
console.log(list2);
list2.then(console.log);
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} 먼저 리턴
// 1 (1초 후)
// 2 (다시 1초 후)
// 3 (다시 1초 후)
// [1, 2, 3]

var list3 = _.each([1, 2, 3], function(v) {
  return _.go(delay(v), console.log); // _.go도 비동기를 제어한다.
});
console.log(list3);
list3.then(console.log);
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} 먼저 리턴
// 1 (1초 후)
// 2 (다시 1초 후)
// 3 (다시 1초 후)
// [1, 2, 3]

var list4 = _.each([1, 2, 3], _.pipe(delay, console.log));  // _.pipe도 비동기를 제어한다.
console.log(list4);
list4.then(console.log);
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined} 먼저 리턴
// 1 (1초 후)
// 2 (다시 1초 후)
// 3 (다시 1초 후)
// [1, 2, 3]

비동기 상황을 제어하기 위해 함수를 선택할 때도 동기 상황에서 함수를 선택할 때와 동일한 기준으로 선택하면 된다. _.each는 내부를 순회하면서 루프를 끝까지 돌고 싶을 때 사용하면 된다. _.each는 받은 iteratee의 결과가 나올 때까지 기다렸다가 다음 iteratee를 실행한다. 반복되는 일들이 반드시 순서대로 수행되어야 할 때 사용하면 된다.

7.4.3 실행한 후의 결과 담기

_.map은 데이터베이스에 각각 update 문을 실행한 후의 결과를 만든다거나 할 때 유용하다.

[코드 7-32] _.map
var update = function(id) {
  return delay({ id: id, updated_at: new Date() });
};

var ids = [5, 10, 20];
_.go(ids,
  _.map(function(id) { return update(id); }),
  JSON.stringify,
  console.log);

// [{"id":5,"updated_at":"2017-04-07T16:25:53.626Z"},
//  {"id":10,"updated_at":"2017-04-07T16:25:54.627Z"},
//  {"id":20,"updated_at":"2017-04-07T16:25:55.629Z"}]
// 1초씩 증가

7.4.4 true를 만나면 그만 돌기

하나씩 수행하다 특정 상황을 만났을 경우에 그다음 일을 하지 않도록 해야 한다면 _.find 함수가 적합하다. 멈춰야 할 곳을 찾으면 된다.

[코드 7-33] _.find
_.find([10, 20, 50, 100], function(v, i) {
  console.log(v, i);
  return delay(v > 30);
});
// 10 0
// 20 1 (1초 후)
// 50 2 (다시 1초 후)
// 끝

// 100 3 은 찍히지 않는다.

함수도 값이므로 함수들을 미리 담아 특정 상황까지만 수행되도록 할 수도 있다.

[코드 7-34] _.find에 함수들을 담아 중간에 멈추기
var a = 5;
_.find([
  function() {
    console.log(false);
    return delay(false);
  },
  function() {
    console.log("a == 10");
    return delay(a == 10);
  },
  function() {
    console.log("1 < 2");
    return delay(1 < 2);
  },
  function() {
    console.log(true);
    return delay(true);
  },
], function(fn) { return fn(); });
// false
// a == 10 (1초 후)
// i < 2   (다시 1초 후)
// 끝

7.4.5 _.if _.some, _.every 조합하기

_.some이나 _.every는 배열 등에 담긴 값을 순회하며 predicate를 실행한 후 boolean 값을 리턴하는 함수다. 두 함수 모두 데이터를 순회하면서 각각 한 번씩 비동기 상황을 끝낸 후의 결과를(데이터베이스를 조회하거나 갱신한 후의 결과를) 확인한다. _.every라면 false를 만났을 때 멈추고 _.some이라면 true를 만났을 때 멈춘다. _.every는 모든 비동기 결과가 true일 때 true이고, _.some은 하나라도 true라면 true다. _.find처럼 특정 조건에 멈추기 위할 때와 참인지 거짓인지 판별할 때 사용한다. _.if 함수와의 조합도 어울린다.

[코드 7-35] _.if _.some, _.every 조합하기
var list = [0, 0, 10, 0, 0, 0];

/* 동기 */
_.go(list,
  _.some(function(val) { return val; }), // 즉시 결과를 리턴한 경우
  _.if(function() {
    console.log('true 면 여기');
  }).else(function() {
    console.log('false 면 여기');
  }));
// 바로 true 면 여기 출력

/* 비동기 */
_.go(list,
  _.some(function(val) { return delay(val); }), // delay 사용
  _.if(function() {
    console.log('true 면 여기');
  }).else(function() {
    console.log('false 면 여기');
  }));
// 3초 후 true 면 여기 출력

_.go(list,
  _.every(function(val) { return delay(val); }), // _.every로 변경
  _.if(function() {
    console.log('true 면 여기');
  }).else(function() {
    console.log('false 면 여기');
  }));
// 1초 후 false 면 여기 출력

var list2 = [2, 3, 10, 4, 5, 2];
_.go(list2, // list2 사용
  _.every(function(val) { return delay(val); }),
  _.if(function() {
    console.log('true 면 여기');
  }).else(function() {
    console.log('false 면 여기');
  }));
// 6초 후 true 면 여기 출력

/* _.if 의 첫 번째 인자로 _.some()이나 _.every()를 사용해도 좋다. */
_.go(list,
  // 첫 번째 부터 false지만 1초 걸린 후 로그를 출력하지 않고 .else_if 로 넘어감
  _.if(_.every(function(val) { return delay(val); }),
    function() {
      console.log('list의 경우 _.every는 false여서 안들어옴');
    })
  // 3초 더 걸린 후 "list의 경우 _.some은 true여서 여기 들어옴" 출력
    .else_if(_.some(function(val) { return delay(val); }),
      function() {
        console.log('list의 경우 _.some은 true여서 여기 들어옴');
      }),
  function() { return list2; },
  // 6초 더 걸린 후 "list2의 경우 _.every가 true여서 여기 들어옴" 출력
  _.if(_.every(function(val) { return delay(val); }),
    function() {
      console.log('list2의 경우 _.every가 true여서 여기 들어옴');
    }));

코드 7-35와 같은 코드를 응용하면 실제로 데이터베이스를 마이그레이션(migration)하는 코드를 작성할 때 매우 편리하다. _.if의 보조 함수로 사용된 함수에서도 _.pipe로 감싸거나 _.go로 실행하거나 Promise를 사용하는 식으로 비동기 제어를 계속 할 수 있다. 조건문을 대신하는 함수인 _.if, ‘하나라도 true라면’의 역할을 하는 _.some, ‘모두 true라면’의 역할을 하는 _.every 같은 함수들로 비동기 상황을 제어하는 것이, Promise만으로 제어하는 것보다 훨씬 편리하다. _.some은 ‘true를 만날 때까지만 수행하라’는 용도로도 사용할 수도 있고 _.every는 ‘false를 만날 때까지만 수행하라’는 용도로도 사용할 수 있다.

Partial.js의 고차 함수들은 비동기 상황을 만나면 내부에서 알아서 로직을 변경하여 비동기를 제어하도록 만들어졌다. 이런 사례들처럼 어떤 로직이든지 간에 독자가 원하는 로직을 고차 함수로 만들어 두면, 더 많은 비동기 상황을 편하게 제어할 수 있을 것이다.

Partial.js의 경우 _.go, _.pipe, _.indent, _.tap, _.if, _.each, _.map, _.filter, _.reject, _.find, _.some, _.every, _.reduce 등의 다양한 고차 함수들이 비동기 상황을 제어하도록 되어 있다. 그리고 216쪽 5.2절에서 확인했던 것처럼 어떤 함수든 파이프라인 사이를 통과하기만 하면 비동기를 자동으로 제어해 준다.

필자는 실무에서 Partial.js의 고차 함수를 이용하여 거의 대부분의 비동기 상황을 효과적으로 제어할 수 있었다. 함수를 고차 함수로 만들고 파이프라인을 통과하는 식으로 코딩을 하면 비동기 상황을 제어하기 매우 편하다.

7.4.6 _.loop와 _.break

다음은 비동기 상황에서 유용한 또 하나의 고차 함수다. 기본적으로 _.reduce와 닮았지만 _.break를 통해 중간에 멈출 수 있다. 원하는 지점에서 멈추고자 한다면 _.find, _.some, _.every로도 가능하겠지만 이 세 가지의 고차 함수는 결과가 정해져 있다. _.find는 첫 번째 인자로 넘긴 배열을 돌며 찾아진 해당 번째 값이고 _.some, _.everyboolean 값이다. _.loop를 이용하면 원하는 때에 멈추면서 그때까지 만들어간 새로운 값을 리턴할 수 있어 _.find, _.some, _.every 와는 다른 로직이 필요할 때 사용할 수 있다.

[코드 7-36] _.loop와 _.break
_.loop([1, 2, 3, 4, 5], function(memo, num, i) {
  return _.go(delay(memo + num), function(memo) {
    console.log(i + "번째", memo);
    return memo > 6 ? _.break(memo) : memo;
  });
}, 0).then(console.log);
// 0번째 1 "loop" (1초 후)
// 1번째 3 "loop" (다시 1초 후)
// 2번째 6 "loop" (다시 1초 후)
// 3번째 10 "loop" (다시 1초 후)
// 10 최종 결과
// 5번째는 돌지 않음

_.loop를 통해 memo를 만들어 가면서 memo가 6보다 크면 _.break로 결과를 감싸주어 멈추도록 했다. _.loop를 기반으로 다양한 고차 함수들을 조합하고 파이프라인과 함께 사용하면 꽤 복잡한 로직들을 굉장히 단순하게 만들어갈 수 있다.

필자가 하고 싶은 이야기는 이런 함수적 접근이 매우 실용적이라는 것이다. _.loop 함수도 _.find 함수로 구현한 것이다. 콜백 지옥만이 비동기 지옥이 아니며 콜백 지옥 외에 다양한 비동기 지옥의 난이도를, 미리 구현된 함수들을 조합하는 식으로 낮출 수 있다. 그리고 이런 방식으로 코딩하면 동기든 비동기든 상관없이 완전히 동일한 구조의 코드를 만들 수 있다.

7.4.7 async await 그리고 Babel은 모든 비동기 상황의 해결책일까?

async await 키워드를 이용하면 Promise 객체를 리턴하는 함수들을 이용하여 훨씬 간결하게 비동기 상황을 제어할 수 있고, 동기 코드와 거의 동일하게 작성할 수 있다. 하지만 모든 상황을 지원하지는 않는다. 물론 자바스크립트에서 async await가 지원되는 것은 매우 기쁜 일이다. 그렇지만 앞서 말한 대로 async await는 모든 비동기 상황의 해결책도 아니고 동기로 동작하는 함수와 함께 사용하는 것이 불가능하다. 하나씩 살펴보자.

우선 정상 동작하는 코드를 보자.

[코드 7-37] test1
async function test1() {
  var a = await delay(1000);
  return a + 1000;
}

test1().then(function(result) {
  console.log(result);
  // 2000
});

잘 동작한다. 위 코드를 Babel은 어떻게 컴파일할까? 다음과 같다.

[코드 7-38] Babel - test1
var test1 = function () {
  var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
    var a;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return delay(1000);

          case 2:
            a = _context.sent;
            return _context.abrupt("return", a + 1000);

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));

  return function test1() {
    return _ref.apply(this, arguments);
  };
}();

function _asyncToGenerator(fn) {
  return function () {
    var gen = fn.apply(this, arguments);
    return new Promise(function (resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(function (value) {
            step("next", value);
          }, function (err) {
            step("throw", err);
          });
        }
      }

      return step("next");
    });
  };
}

test1().then(function (result) {
  console.log(result);
  // 2000
});

위 코드를 Partial.js로는 아래처럼 코딩할 수 있다.

[코드 7-39] Partial.js - test1
var test1 = _.async(
  function() {
    return delay(2000);
  },
  function(a) {
    return a + 2000;
  });

test1().then(function(result) {
  console.log(result);
  // 4000
});

// 혹은 _.go(delay(2000), function(a) { return a + 2000; });

7.4.8 async await를 for문과 if문 등에서 사용하기

async awaitfor문이나 if문 사이에서 사용해도 잘 동작한다.

[코드 7-40] test2
async function test2() {
  var list = [1, 3, 5, 6, 7, 9];
  for (var i = 0; i < list.length; i++) {
    var value = await delay(list[i]);
    console.log(value); // 1초 마다 한 번씩 실행
    if (await delay(value % 2 == 0)) return list[i];
  }
}

test2().then(function(result) {
  console.log(result);
  // 6
})

원하는 대로 잘 동작했다. 물론 위 코드를 Babel로 컴파일할 경우 매우 복잡해지지만, 원래는 for문 사이에서나 if 사이에서 비동기 함수의 결과를 동기적으로 기다리지 못하기에 위 사례는 충분한 이점이 있다고 볼 수 있다.

7.4.9 async await의 한계

하지만 async await로 잡을 수 없는 부분이 있다. 동기 함수 혹은 메서드와의 협업이 불가능하다. async await의 문제라기보다는 원래 Promise의 특징이 그렇고 애초에 async await는 동기 함수와 함께 사용하려고 만들어진 것이 아니기 때문이다. 이점을 알고 사용해야 실수가 없을 것이다. 다음을 보자.

[코드 7-41] test3
async function test3() {
  var list = [1, 3, 5, 6, 7, 9];
  return list.map(async function(val, i) {
    console.log(val, i); // 동시에 모두 실행됨
    return await delay(val * 10); // 동시에 모두 실행됨
  });
}

test3().then(function(result) {
  console.log(result);
  // [Promise, Promise, Promise, Promise, Promise, Promise]
  // 결과로 바뀌지 않은 Promise 들
});

7.4.10 함수를 값으로 다루는 고차 함수의 해법

async await를 어떻게 잘 배치해도 동기 함수와는 조합할 수 없다. Promise가 그렇듯 말이다. Partial.js를 함께 사용하면 해결할 수 있다. Partial.js는 Promise 없이도 비동기를 제어할 수 있지만 Promise를 대척하는 기법이 아니다. Promiseasync await를 더 잘 사용할 수 있도록 도와주는 라이브러리다.

[코드 7-42] Partial.js + async await - test3
async function test3() {
  var list = [1, 3, 5, 6, 7, 9];
  return _.map(list, async function(val, i) {
    console.log(val, i); // 1초씩 순차적으로 실행됨
    return await delay(val * 10); // 1초씩 순차적으로 실행되고 정상적으로 결과를 꺼냄
  });
}

test3().then(function(result) {
  console.log(result);
  // [10, 30, 50, 60, 70, 90]
});

Partial.js는 async await 키워드나 Promise가 없어도 완전히 동기 코드와 동일한 코드 작성이 가능하다.

[코드 7-43] Partial.js - test3
function test3() {
  var list = [1, 3, 5, 6, 7, 9];
  return _.map(list, function(val, i) {
    console.log(val, i); // 1초씩 순차적으로 실행됨
    return delay(val * 10); // 1초씩 순차적으로 실행되고 정상적으로 결과를 꺼냄
  });
}

test3().then(function(result) {
  console.log(result);
  // [10, 30, 50, 60, 70, 90]
});

// 사실 파이프라인을 사용하면 then도 없어도 된다.
_.go([1, 3, 5, 6, 7, 9],
  _.map(function(val) { return delay(val * 10); }),
  console.log);

// 화살표 함수와 함께라면 더 예쁘다.
_.go([1, 3, 5, 6, 7, 9], _.map(val => delay(val * 10)), console.log)

Promise, async, await가 자바스크립트의 모든 비동기 상황에서의 문제를 풀어 주지는 않는다. 보다 풀기 쉽게 하는 하나의 단위를 만들어 줄 뿐이다. 그것을 활용하여 다양한 비동기 상황에 맞는 좋은 로직을 만드는 일은 여전히 개발자의 몫으로 남아 있다.

아직 Promise는 IE11에서 지원되지 않는다. 브라우저에서 Promise를 사용하려면 Promise를 구현한 라이브러리가 필요하다. 비동기 제어를 위한 라이브러리인 bluebird.js는 5,598줄에 78kb(compressed)이고 Partial.js는 1,800줄에 42kb (compressed)다. Partial.js의 파이프라인과 고차 함수들을 활용하면 비동기 코드를 동기 코드와 동일한 코드로 작성할 수 있고 복잡한 로직도 쉽게 구현할 수 있다.

비동기를 제어하는 일에 Promise보다 중요하고 핵심적인 개념은 일급 함수, 클로저, 재귀, 함수 나열, 이벤트 루프 등이다. Promise 지원 여부나 자바스크립트의 발전 여부와 상관없이 로직을 잘 다루는 좋은 함수를 만들어 두는 일은 앞으로도 계속 필요한 일이다. 재귀 등의 기본 개념이나 자바스크립트의 함수 기능 연습을 많이 하는 것이 중요하다. 그렇게 되면 자연스럽게 Promise도 아무런 어려움 없이 더욱 잘 사용할 수 있다.

목차

  1. 함수형 자바스크립트 소개
    1. 함수형 자바스크립트 그거 먹는 건가요?
    2. 함수형 자바스크립트의 실용성
    3. 함수형 자바스크립트의 실용성 2
    4. 함수형 자바스크립트를 위한 기초
  2. 함수형 자바스크립트를 위한 문법 다시보기
    1. 객체와 대괄호 다시 보기
    2. 함수 정의 다시 보기
    3. 함수 실행과 인자 그리고 점 다시보기
    4. if else||&& 삼항 연산자 다시 보기
    5. 함수 실행의 괄호
    6. 화살표 함수
    7. 정리
  3. Underscore.js를 직접 만들며 함수형 자바스크립트의 뼈대 익히기
    1. Underscore.js 소개
    2. _.map과 _.each 구현하기
    3. _.filter, _.reject, _.find, _.some, _.every 만들기
    4. _.reduce 만들기
    5. 좀 더 발전시키기
  4. 함수 조립하기
    1. 고차 함수와 보조 함수
    2. 부분 적용
    3. 연속적인 함수 실행
    4. 더 나은 함수 조립
  5. Partial.js와 함수 조립
    1. 파이프라인
    2. 비동기
    3. 고차 함수
    4. 파이프라인2
    5. 템플릿 함수
    6. 지연 평가와 컬렉션 중심 프로그래밍
  6. 값에 대해
    1. 순수 함수
    2. 변경 최소화와 불변 객체
    3. 기본 객체 다루기
    4. 정리
  7. 실전에서 함수형 자바스크립트를 더 많이 사용하기
    1. _.each, _.map
    2. input tag들을 통해 form data 만들기
    3. 커머스 서비스 코드 조각
    4. 백엔드와 비동기
  8. 함수형으로 만드는 할 일 앱
    1. 할 일 앱 만들기(1)
    2. 할 일 앱 만들기(2)
  9. 메모이제이션
    1. memoize 함수
    2. 메모이제이션과 불변성, 그리고 할 일 앱
    3. 마무리 하며
Clone this wiki locally