Javascript

[JS] promise, async, await 쓰지 않고 parallel, waterfall 구현하기

Alexim 2022. 6. 3. 00:20

프렙 과정에서 과제로 나왔던 parallel, waterfall 이 과제를 하면서 비동기에 대해 많이 배우게 되었다.

이번에는 이 과제를 promise를 사용해서 구현해 보았는데 이걸 기록해 놓으려 한다.

 

1. Parallel 병렬

function parallel (tasks, finalCallback) {}

수행해야 하는 함수들이 모여있는 tasks, tasks들이 모두 다 수행되고 나서 마지막으로 수행되어야 하는 finalCallback 

 

테스트코드

const order = [];
parallel(
      [
        function (callback) {
          setTimeout(function () {
            order.push(1);
            callback(1);
          }, 150);
        },
        function (callback) {
          setTimeout(function () {
            order.push(2);
            callback("2");
          }, 300);
        },
        function (callback) {
          setTimeout(function () {
            order.push(3);
            callback({ data: 3 });
          }, 10);
        },
      ],
      function () {
        console.log(order == [4, 2, 1]);
      }
    );

 

처음에 했던 방법

1. JS로만 구현하기

function parallel(tasks, finalCallback) {
 var count = 0;
 var results = [];

 if (!tasks.length) {
  return finalCallback();
 }

 for (var i = 0; i < tasks.length; i++) {
  asyncHandler(i);
 }

 function asyncHandler(i) {
  tasks[i]((result) => {
  count++;
  results[i] = result;
   if (count === tasks.length) {
    finalCallback(results);
   }
  })
 }
}
for (var i = 0; i < tasks.length; i++) {
 tasks[i]((result) => {
 results[count] = result;
 count++;
 if (results.length === tasks.length) {
  finalCallback(results);
 }
})

for문에 asyncHandler를 넣어도 되지만 과제에서는 var를 써야 했고 results 배열안의 순서가 tasks의 순서와 같아야 했다. 그리고 tasks 모두가 실행되면 마지막 finalCallback를 전달해야 했다.

tasks는 setTimeout 때문에 3, 1, 2의 순서를 가진다. var 때문에 i의 값이 3이라서 count 변수를 만들어 1씩 증가시켜 사용한다고 해도 results 배열의 순서가 1, 2, 3이 될 수 없다. for문에서는 i의 값만 asyncHandler에 전달시키거나 즉시실행함수를 사용하는 방법밖에 없다. (방법이 더 있을 수도.. )

 

2. 즉시실행함수 사용

function parallel(tasks, finalCallback) {
 var count = 0;
 var results = [];

 if (!tasks.length) {
  return finalCallback();
 }

 for (var i = 0; i < tasks.length; i++) {
  (function (i) {
   tasks[i]((result) => {
   count++;
   results[i] = result;
   if (count === tasks.length) {
	finalCallback(results);
   }
  })
 })(i)
}
}

3. Promise 사용해서 구현하기

async function parallel(tasks, finalCallback) {
  const asyncTasks = tasks.map((task) => {
    return new Promise (resolve => {
      task((result) => {
        resolve(result);
      })
    })
  });

  await Promise.all(asyncTasks).then((result) => {
    finalCallback(result);
  })
}

아직 사용하는 게 서툴러서 잘 쓴 건지 잘 모르겠다. 나중에 익숙해지면 고칠 점이 보이겠지.

promise를 사용하는 게 더 쉬울 줄 알았는데 똑같이 어려웠다. await을 쓰려면 tasks 함수에 promise가 있어야 하는데 promise를 넣는 부분에서 많이 헤맸다. Promise.all도 처음 써봐서 then을 쓰지 않고 끝나야 하는 건 줄 알았다. 😂

Promise.all에서 then을 쓰면 배열 안의 값이 한 번에 적용이 되는 게 신기했다.

 

2. Waterfall 

 

테스트코드

const order = [];

window.waterfall(
  [
    function taskOne(callback) {
      setTimeout(function oneDone() {
        order.push(1);
        callback("첫 번째 결과");
      }, 250);
    },
    function taskTwo(one, callback) {
      expect(one).to.eql("첫 번째 결과");

      setTimeout(function twoDone() {
        order.push(2);
        callback();
      }, 100);
    },
    function taskThree(callback) {
      setTimeout(function threeDone() {
        order.push(3);
        callback("최종 결과");
      }, 10);
    },
  ],
  function finalCallback(result) {
   console.log(order == [1, 2, 3]);
   console.log(result === "최종 결과");
  }
);

 

1. JS로만 구현하기

function waterfall2(tasks, finalCallback) {
  var count = 0;
  var results = [];

  if (!tasks.length) {
    finalCallback();
  }

  tasks[0]((result) => {
    callback(result);
  });

  function callback(result) {
    results[count] = result;
    count++;

    if (count === tasks.length) {
      return finalCallback(results.splice(-1).join(""));
    }

    results[count-1] ? tasks[count](results[count-1], callback)
    : tasks[count](callback);
  }
}

waterfall에서 제일 중요했던 건 tasks의 함수 하나가 끝나고 다음 함수가 실행되어야 하므로 callback 함수에서 계속 함수를 실행시켜야 한다는 거였다.

 

2. Promise 사용해서 구현하기

async function waterfall(tasks, finalCallback) {
  let storedResult;

  function asyncTasks(index, result) {
    return new Promise(resolve => {
      if (storedResult) {
        tasks[index](result, (value) => {
          storedResult = value;
          resolve(value);
        })
      } else {
        tasks[index](value => {
          storedResult = value;
          resolve(value);
        })
      }
    }).then((result) => {
      index++;
      asyncTasks(index, result);
    }).catch(() => {
      finalCallback(result);
    })
  }

  asyncTasks(0);
}

if문에 중복이 생긴 것 같아서 고치고 싶은데 나중으로 조금 미루기..

코드가 이게 맞나 싶을 정도로.. 복잡해 보이는데 지금은 어떻게 고쳐야 할지 모르겠다🤔 then에서 asyncTasks 함수를 계속 호출하는 걸로 만들었는데 흠.. 다음에 다시 고쳐보는 걸로🙄!