# 비동기 제어

# go 함수 비동기 제어

  • go 함수는 reduce를 기반으로 하기 때문에 reduce 함수를 수정하도록합니다.
// 들어오는 데이터가 프로미스라면 then 이후 나온 결과값을 가지고 두번째 들어온 함수 실행
const goPromise = (a, f) => (a instanceof Promise ? a.then(f) : f(a));

const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  } else {
    iter = iter[Symbol.iterator]();
  }

  return goPromise(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      acc = f(acc, a);
      if (acc instanceof Promise) {
        return acc.then(recur);
      }
    }
    return acc;
  });
});

go(
  Promise.resolve(1),
  a => a + 10,
  a => a + 1000,
  a => a + 100000,
  log
).catch(a => console.log(a)); // 101011

go(
  Promise.resolve(1),
  a => a + 10,
  a => Promise.reject("error"),
  log
).catch(a => console.log(a)); // error

# map 함수 비동기 제어

go(
  [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
  L.map(a => a + 10)
  log
).catch(a => console.log(a));
// [promise, promise, promise]

위처럼 map 함수에 Promise를 인자로 받은 경우, map함수에 비동기 처리를 하지않아 기다리지 않고, 바로 promise pending 상태로 리턴합니다.

그래서 map함수에 promise값을 핸들링 가능하도록 합니다. 위에서 쓴 goPromise함수를 응용합니다.

const goPromise = (a, f) => (a instanceof Promise ? a.then(f) : f(a));

L.map = curry(function*(f, iter) {
  for (const a of iter) {
    // goPromise함수에 의해 프로미스를 받은 값은 대기 후 실행 결과 리턴
    yield goPromise(a, f);
  }
});

# take 함수 비동기 제어

위처럼 map가 promise를 받아줬다면, map에서 리턴하는 값을 최종 제어하는 take 함수도 비동기제어를 하도록 합니다.

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return (function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      // 들어오는 값이 promise면 then이후 값을 push, 다음 값이 promise가 아닐 수 있으니 재귀로 평가
      if (a instanceof Promise)
        return a.then(a => ((res.push(a), res).length === l ? res : recur()));
      if ((res.push(a), res).length === l) return res;
    }

    return res;
  })();
});

go(
  [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
  L.map(a => a + 10),
  take(2),
  log
); // [11, 12]

# filter 함수 비동기 제어

아래의 코드를 실행하기 위해서는 filter에 대해서 비동기 값을 핸들링 해야합니다.

go(
  [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
  L.map(a => Promise.resolve(a * a)),
  L.filter(a => Promise.resolve(a % 2)),
  take(2),
  log
);

현재 코드에서 실행할 경우 filter에서는 비동기 값을 제어하지 않아 promise<pending>값이 뜨게 됩니다.

const filter = (f, iter) => {
  let res = [];
  for (const a of iter) {
    // 여기에서 a 값이 promise이기 때문이죠, 위에서 정의한 goPromise 함수를 활용합니다.
    if (f(a)) res.push(a);
  }
  return res;
};
const nop = Symbol("nop");

L.filter = curry(function*(f, iter) {
  for (const a of iter) {
    const b = goPromise(a, f);
    // b는 아직 promise그래서 분기처리, 다음 함수로 값을 보내지 않을땐 promise.reject
    // promise.reject값이 에러핸들링인지 무시인지 모르기때문에 nop을 정의하고, promise.reject 처리합니다 (take에서)
    if (b instanceof Promise) yield b.then(b => (b ? a : Promise.reject(nop)));
    else {
      if (b) yield a;
    }
  }
});

위처럼 filter함수에서도 promise값을 핸들링하였고, 이후, take함수에서도 promise 값을 받아주도록 합니다.

take내에 들어오는 값이 promise일 경우 then으로 값을 풀고, 푼 값에 에러가 있고 그 값이 nop일 경우 다음 iter를 실행합니다.

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return (function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      // 들어오는 값이 promise면 then이후 값을 push, 다음 값이 promise가 아닐 수 있으니 재귀로 평가
      // promise에 들어온 값이 위에서 날린 nop면 recur 실행해서 값 무시
      if (a instanceof Promise)
        return a
          .then(a => ((res.push(a), res).length === l ? res : recur()))
          .catch(e => (e === nop ? recur() : Promise.reject()));
      if ((res.push(a), res).length === l) return res;
    }

    return res;
  })();
});
최근변경일: 9/23/2024, 2:14:43 AM