go, pipe, reduce에서 비동기 제어

nn·2023년 6월 26일
0

go

go함수는 인자를 여러개 전달받아 함수에게 순차적으로 전달하면서 값을 반환하는 함수이다.

  const go = (...args) => reduce((a, f) => f(a), args);

go를 통해 아래와 같이 함수를 순차적으로 실행한 결과를 얻을 수 있다.

 go(1,
    a => a + 10,
    a => a + 100,
    a => a + 1000,
 log) // 1111 출력

하지만 비동기 처리가 되어있지 않기 때문에 프로미스가 리턴되는 경우엔 이런 결과를 나타낸다.

인자에 값이 아닌 프로미스가 리턴되므로 정확한 결과를 내지 못한다.

즉, go에 있는 reduce에서 전달되는 인자가 값만 전달되도록 수정하면 될 것이다.

 const reduce = (fn, acc, iter) => {
   if (!iter) {
     iter = acc[Symbol.iterator]();
     acc = iter.next().value;
   }
   iter = iter[Symbol.iterator]();
   let cur;
   while (!(cur = iter.next()).done) {
     const a = cur.value;
     acc = fn(acc, a);
   }
   return acc;
 };

reduce함수는 위와 같이 생겼다.

acc가 인자로 들어온 현재 값이 될 것이다.
acc가 프로미스로 들어온 경우를 제어해보자.


  const reduce = (fn, acc, iter) => {
    if (!iter) {
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    } else {
      iter = iter[Symbol.iterator]();
    }
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      acc = acc instanceof Promise ? acc.then(acc => fn(acc, a)) : fn(acc, a);
    }
    return acc;
  };

잘 작동하지만 뭔가 이상하다.

 go(1,
    a => a + 10,
    a => Promise.resolve(a + 100),
    a => a + 1000,
    log)

Promise.resolve(a + 100)이후부터는 모두 프로미스를 리턴하게 되므로 동기적으로 처리되어도 되는 a => a + 1000또한 비동기로 처리되기 때문이다.

유명함수를 만들어 재귀적으로 사용하여 해결해보자.

 const reduce = (fn, acc, iter) => {
   if (!iter) {
     iter = acc[Symbol.iterator]();
     acc = iter.next().value;
   } else {
     iter = iter[Symbol.iterator]();
   }
   return function recur(acc) {
     let cur;
     while (!(cur = iter.next()).done) {
       const a = cur.value;
       acc = fn(acc, a);
       log(acc);
       if (acc instanceof Promise) {
         return acc.then(acc => recur(acc));
       }
     }
     return acc;
   }(acc);
 };

즉시 실행하는 유명함수를 즉시 실행해서 리턴하도록 했다.

그러므로

 go(1,
    a => a + 10,
    a => Promise.resolve(a + 100),
    a => a + 1000,
    log)

a => a + 10에서는 recur에 들어간 acc가 프로미스가 아니기 때문에 then을 일으키지 않는다.
그리고 a => Promise.resolve(a + 100) 후에는 acc 가 프로미스이므로 than을 통해 recur를 한번 더 실행하게 된다.
이 때 recur의 인자에 들어가는 값은 then에서 얻은 값인 111이 된다.

그럼 recure에서 프로미스에서 꺼낸 값을 바로 fn함수를 실행시키고 리턴하여 다음 a => a + 1000의 인자로 넘긴다.


하지만 문제가 또 있다.
지금은 go의 첫번째 인자가 프로미스가 아닌 경우에만 잘 작동한다.

예를 들어

 go(Promise.resolve(1),
    a => a + 10,
    a => Promise.resolve(a + 100),
    a => a + 1000,
    log)

인 경우엔 작동이 되지 않는다.

첫번째 인자부터 프로미스인 경우를 해결하기위해선 go1 함수를 사용할 수 있다.

const go1 = (a, fn) => {
	if(a instanceof Promise) {
    	return a.then(fn);
    } else {
    	return fn(a);
    }
}

이 go1 함수는 recur와 함께 사용해서 처음 들어온 인자부터 확인할 수 있다.
처음 들어온 인자는 go1함수를 통해 promise인지 확인하게 된다.

  const reduce = (fn, acc, iter) => {
    if (!iter) {
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    } else {
      iter = iter[Symbol.iterator]();
    }
    return go1(acc, function recur(acc) {
      let cur;
      while (!(cur = iter.next()).done) {
        const a = cur.value;
        acc = fn(acc, a);
        if (acc instanceof Promise) {
          return acc.then(acc => {
            return recur(acc)
          });
        }
      }
      return acc;
    });
  };

이렇게 하면 recur는 항상 프로미스가 풀린 채로 실행되게 된다.


reject

  go(1,
    a => a + 10,
    a => Promise.resolve(a + 100),
    a => Promise.reject("error!!"),
    a => log(a),
    a => a + 1000,
    log)

이렇게 만약 중간에 에러가 나는 경우엔 이후의 함수를 실행하지 않고 에러만 나타나게된다.

  go(1,
    a => a + 10,
    a => Promise.resolve(a + 100),
    a => Promise.reject("error!!"),
    a => log(a),
    a => a + 1000,
    log).catch(e => log(e))

catch로 에러를 제어할 수 있다.

profile
내가 될 거라고 했잖아

0개의 댓글