[JS] 비동기,동기 처리

Pavel_Dmr·2022년 6월 23일
2

JavaScript

목록 보기
7/9

🍖 비동기 처리방식

자바스크립트에서는 거의 대부분의 작업들이 비동기로 이루어진다.(물론 자바스크립트 자체는 동기방식 언어이다. 다만 자바스크립트 런타임이 DOM조작이나 AJAX 같은 비동기 처리를 위한 web API을 제공한다.) 자바스크립트에서 비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고,다음 코드를 먼저 실행하는 자바스크립트의 특성을 의미한다. 동시에 여러 작업을 처리할 수 있고, 기다리는 과정에서 다른 함수를 호출 할 수도 있다.

setTimeout 메서드

setTimeout()은 가장 대표적인 빌트인 내장 비동기 함수입니다. 두개의 파라미터을 받는데, 첫번째 인자는 수행할 작업 내용을 가진 콜백함수이고, 두번째 인자는 첫번째인자로 들어온 콜백함수의 수행 지연 시간을 지정해줍니다.

setTimeout(() => console.log("Before"), 3000);
console.log("After");

//After => Before 순으로 로그 찍힘

비동기 방식이기 때문에, 다른 소스는 기다려주지않는다...만약 비동기 처리을 동기 방식처럼 처리 하고 싶으면 아래와 같은 방법으로 처리 해야한다.


Promise,Then 객체

ES6.ver
Promise는 자바스크립트 비동기 처리에 사용되는 객체입니다.
여기서 자바스크립트의 비동기 처리란 "특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성"을 의미합니다.

promise는 그럼 왜 필요한가. promise는 주로 서버에서 받아온 데이터를 화면에 표시할 때 사용합니다. 우리가 서버에 데이터를 요청하면, 데이터 로드가 다 된후에 우리가 보는 화면에 데이터 표시가 되야한다. 만약 로드가 다 되지 않은 상태로 화면에 데이터를 표시하려고 하면 오류가 날것이다.

결론적으로 promise는 비동기 처리를 동기 처리처럼 사용하기 위해 고안된 객체이다.
비동기 작업을 순차적으로 처리하거나, 병렬로 진행하는 등 작업을 더 수월하게 처리 할 수 있고, 예외 처리을 더 쉽게 할 수 있게 도운다.


var promise = function (param) {
  return new Promise(function (resolve, reject) {
    //비동기 표현을 위한 setTimeout
    window.setTimeout(function () {
      //참이면
      if (param) {
        resolve("완료");
      } else {
        reject(Error("실패"));
      }
    }, 3000);
  });
};

//promise 실행
promise(true).then((text) => {
  console.log(text);
}, (error) =>{
  console.error(error);
})

Promise은 약속이라고 볼 수 있다. 작업을 수행하고 수행결과에 따라 약속이 지켜졌는지,안 지켜졌는지 따라 특정한 상태을 부여하고, 작성한 코드에 따라 결과,error,행동을 리턴하거나 수행 할 수 있다.

promise는 상태라는 개념으로 비동기 작업의 과정의 상태을 나타낸다.

상태의 분류는 이와 같다.

Pending(대기)
비동기 처리 로직이 아직 완료되지 않은 상태. 즉 대기중인 상태

Fulfilled(이행)
비동기 처리가 완료되어, Promise가 결과 값을 반환해준 상태

Rejected(실패)
비동기 처리가 실패하거나, 오류가 발생한 상태

Settled(처리)
이행,실패 둘중 어느 상태을 수행하던,Promise을 처리됐다고 인지한다.

그러면 이러한 상태들이 있고, 실 소스코드에서는 어떠한 방식으로 진행이 되는 것일까?

위의 Promise 선언부에서, Promise 객체를 생성하기 위해 Promise 객체를 리턴하도록 익명함수로 감싸고 있다.

Promise 객체에서도 파라미터로 익명함수를 담고 있고, 익명 함수는 resolve와 reject를 파라미터로 받는다.

Promise 객체는 파라미터 값으로 반드시,resolve와 reject을 파라미터로 갖는 함수을 넣어줘야 한다.

new Promise에서 Promise가 생성되는 직후부터 resolve나 reject가 호출 되기 전에 까지가 Pending(대기) 상태라고 생각하면 된다.

이후 비동기 작업이 수행 된 후, 결과물이 제대로 리턴 되면, 함수의 첫번째 파라미터인 resolve 함수을 호출하고,실패하면 reject 함수를 호출한다는 것이 promise의 기본 루틴이다.

promise(true).then(
  //첫번째 파라미터,이행 되었을 시
  (text) => {
    console.log(text);
  },
  //두번째 파라미터,실패 되었을 시
  (error) => {
    console.error(error);
  }
);

Promise의 선언부는 이러하고 실질적으로 사용하는 실행부을 살펴보면,Then이라는 메소드가 존재한다.

then()은 비동기 작업이 Settled(처리) 되었을 때, 호출되는 메소드다. then() 메소드는 API 정의상, 첫번째 파라미터엔 Fulfilled 이행 되었을 때의 함수 정의을, 두번째 파라미터엔 rejected 되었을때의 함수을 정의한다.

function increase(number) {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = number + 10;

      if (result > 50) {
        const error = new Error("50제한 돌파");
        return reject(error);
      }
      resolve(result); //number값에 +10 성공
    }, 1000);
  });
  return promise;
}
increase(0)
  .then((number) => {
    console.log(number);
    return increase(number); //promise를 리턴하면
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  })
  .then((number) => {
    console.log(number);
    return increase(number);
  });

then()을 이용하면 필요한 작업을 진행 하고, Promise을 리턴해줌으로써, 순차적인 비동기 작업 수행이 가능하다. 다음코드는 number값을 10씩 늘리며, 순차적으로 console.log에 number값을 띄워주는 코드이다. return Promise 함수에 증가된 number값을 순차적으로 넣어줌으로써 작업을 수행한다.


에러을 잡아주는 Promise.catch API

then() 이외에 오류나 예외처리을 돕는 메서드가 또 있다. 위에 같은 순차적으로 비동기 작업을 처리하는 코드가 체이닝 형태로 연결되어 있는 상황에서, 중간에 에러가 나면 어떻게 처리해야하는가.

catch()을 사용하면 특정 체이닝 연결 구간에서 발생한 오류을 검출 할수 있다.

asyncThing1()
  .then(() => {
    return asyncThing2();
  })
  .then(() => {
    return asyncThing3();
    // 앞의 3개의 메서드에서 하나라도 오류가 나면, catch에 오류을 잡는다.
  })
  .catch((err) => {
    return asyncRecovery1();
  })
  .then(
    () => {
      return asyncThing4;
    },
    (err) => {
      return asyncRecovery2();
    }
  )
  .catch((err) => {
    return asyncRecovery3();
  })
  .then(() => {
    console.log("작업 완료");
  });

then()의 두번째 파라미터로도 오류 검출이 가능하지만, catch을 쓰면 좀더 직관적인 확인이 가능하기도 하고, 유연하게 오류 처리을 할 수 있다.


다수의 프로미스가 처리 되었을 때, 실행하고 싶으면 Promise.all

여러개의 비동기 작업을 진행 중이고, 이들이 모두 완료되었을 때 작업을 진행하고 싶다면, all()을 사용하면 된다.

var promise1 = new Promise((resolve, reject) => {
  window.setTimeout(() => {
    console.log("첫번째 프로미스 완료");
    resolve("1번째");
  }, Math.random() * 20000 + 1000);
});

var promise2 = new Promise((resolve, reject) => {
  console.log("두번째 프로미스 완료");
  resolve("2번째");
}, Math.random() * 10000 + 1000);

Promise.all([promise1, promise2]).then((values) => {
  console.log("모두 완료됨", values);
});

해당 코드에서는 all() 파라미터로 promise 객체가 든 배열을 가지며,배열 내 프로미스 전부가 Settled 되었을때, then() 메소드가 수행된다.

참고로 all() 파라미터의 배열 내 Promise 객체을 선언할때는 값을 전달하는 방식이 return으로 인한 반환이 아닌 new Promise로 생성자 객체 자체가 되어야한다. all() 메소드에서 Promise 객체 자체을 필요로 하기 때문에...

또한 return이 아닌 방식으로 구현하면 변수 할당 없이 즉각적으로 간단하게 실행하는 방식으로 사용가능하다.

new Promise((resolve, reject) => {
  if (+new Date() % 2 === 0) {
    resolve("Stuff worked!!");
  } else {
    reject(Error("It broke"));
  }
})
  .then(console.log)
  .catch(alert);__

즉석으로 Promise 객체을 생성했고,then,catch을 이용해서 수행하는 코드을 만들었다.


async await 비동기 문법

ES8.ver
async/await 키워드를 사용하면 좀더 앞서 promise에서 다룬 코드보다 직관적이고, 지연 함수를 좀더 간략하게 호출할수 있습니다.

async function test() {
  console.log("before");
  await sleep(3000);
  console.log("after");
  console.log("done!");
}

함수명 또는 변수명(함수로 선언된) 앞에 async을 쓰고, 함수 내부에서 처리을 기다릴 함수 앞에 await을 작성합니다.

또한 앞서 작성한 promise을 리턴하는 increase 콜백함수의 then을 대체해서 이런식으로 작성할 수 있습니다.

async function run(){

    try {
        let result = await increase(0);
        console.log(result);
        result =  await increase(result);
        console.log(result);
        result =  await increase(result);
        console.log(result);
        result =  await increase(result);
        console.log(result);
        result =  await increase(result);
        console.log(result);
        result =  await increase(result);
        console.log(result);
    } catch (error) {
       console.log(error);
    }
}
run();

좀더 직관적이고 간단하게 작성되었습니다. async로 정의된 함수내에서 await로 동기 방식으로 진행되고 함수 외부에선 여전히 비동기로 처리됩니다.


🍱 그래서 동기 방식은?

알다시피, 비동기 방식은 요청한 결과가 반환 되는 동안 다른 작업을 할수가 있다. 프로그램 설계의 대부분은 비동기 처리 방식이 차지 한다. 동기방식은 꼭 필요한 부분에서만 사용해야 한다. 왜라고 물어보면, 당연히 최적화 때문입니다. 물론 비동기 처리방식이 설계가 복잡하고 논증적일 수는 있지만, 동기식보다 많은 장점이 있죠.

동기식도 장점이 있지만, 퍼포먼스가 중요한 프로그램 개발 로직에는 비동기 처리가 적합할 수 있습니다.

반면에 DB나 DBMS시스템과 같이 데이터의 무결성이 중요한 데이터기반 서비스및 솔루션은 데이터 정합성을 위해서 비동기가 기피 될수는 있다.

자바스크립트가 기본적으로 동기 기반 언어이기 때문에, 요청한 지연시간 동안 프로그램 응답를 멈추고 싶다면, 가장 간단한 방법으로 반복문이 있다.

function sleep(ms) {
  const wakeUpTime = Date.now() + ms;
  while (Date.now() < wakeUpTime) {}
}

function test() {
  console.log("before");
  sleep(3000);
  console.log("after");
}

앞서 말했듯이, 퍼포먼스와 치명적인인 연관 관계가 있으니, 동기방식으로 인한 프로그램 지연요청은 반드시 필요할 때만 사용해야 한다.프로그램을 테스트 하는 용도 이외에, 상용환경에서 이러한 지연방식은 기피하는 것이 좋다.

profile
노는게 좋아

0개의 댓글