[TIL_28] <JS> 동기 / 비동기

구민기·2021년 12월 19일
0
post-thumbnail

동기 / 비동기

동기

자바스크립트는 싱글 스레드 기반의 언어이고 이말은 동기식 언어라는 말이다.
동기식(Synchronous) 이라는 것은 한가지 일이 다 끝나고 난 다음에 다음 일은 진행한다고 표현하면 좋을 것같다.

function A() {...};

function B() {...};

function C() {...};

다음과 같은 코드가 있을때 함수A가 실행되고 끝나면 함수B가 실행되고 끝나면 함수C가 실행되는
방식이 동기 방식이다.

웹 페이지를 로드하는 하는 어떤 코드가 있다고 할때 로드하기 전에 데이터를 불러오는 함수가
어떤한 이유로 처리 시간이 오래걸린다고 하면 그 함수가 끝나기 전까지는 페이지가 로드 되지 못하는 문제점이 생긴다.

비동기

위와 같은 문제점을 해결할 수 있는 방식이 비동기 방식이다.
위에 함수A를 비동기 함수라 가정하고 처리되는 과정을 정리하면
함수 A가 호출이되고 함수A의 결과값과 상관없이 함수B가 먼저 실행되고 함수C가 실행된다.
따라서 데이터를 불러오느라 페이지가 로드되지 않는 문제점을 없앨수 있다.

Call Stack / Web API / Task Queue / Event Loop

동기/비동기의 개념에 대해서는 간단하지만 어떠한 과정으로 이루어지는지 좀 더 알아보자.
위에서 자바스크립트는 싱글스레드 기반의 언어라고 했는데 어떻게 비동기 방식으로 처리가되는지 궁금할 것이기 때문에 위의 개념들을 알고 지나가자.

< 출처: https://meetup.toast.com/posts/89 >

  • Call Stack : 자바스크립트 엔진에 있는 저장 공간으로 호출된 함수가 저장되는 공간이라고 생각하면 된다. 호출된 함수들이 저장되고 값을 반환되면 스택에서 제거가 된다.
    자바스크립트는 싱글스레드 언어이기때문에 하나의 스택을 가지고 있다.
  • Web API : 브라우저에서 제공하는 API, DOM 조작이나 AJAX,setTimeout() 같은 비동기 처리를 위한 API를 제공해준다.
  • Task Queue : 비동기 함수에 의해 호출된 함수가 저장되는 공간이다.
    콜스택에 있는 함수가 모두 처리되어 비게 되었을때 추가되어 그때 실행된다.
  • Event Loop : 콜스택이 비어있는지 확인하며 비어있을때 Task Queue에 있는 콜백함수를 꺼내서 콜스택에 추가하는 역할을 해준다.
function A() {
  console.log('A');
};

function B() {
  console.log('B');
};

function C() {
  console.log('C');
};

A();
setTimeout(B, 0);
C();

위 코드처리 과정을 통해 다시 정리해보면
1. 콜스택에 함수A가 추가 되었다가 console.log('A')를 처리하고 콜스택에서 제거된다.
2. setTimeout() 함수가 콜스택에 쌓이고 비동기 함수이기에 Web API에게 넘겨준다.
그리고 함수B가 태스크 큐에 추가된다.
3. 함수C가 콜스택에 추가되고 console.log('C')가 처리되고 콜스택에서 제거된다.
4. 콜스택이 비어있게 되어 이벤트 루프에 의해 태스크 큐에 있던 함수B가 콜스택이 추가되고
5. console.log('B')가 처리된다.

따라서 결과는 A,C,B 순으로 처리된다.

function A() {
  console.log('A');
};

function B() {
  console.log('B');
};

function C() {
  console.log('C');
};

function D() {
  while(true) {
    console.log('D');
  };
};

A();
setTimeout(B, 0);
C();
D()

만약에 위 코드같은 경우가 있다고 가정하면 B가 출력되는일은 없을 것이다.
함수 D가 콜스택에서 제거되는 일은 없을 것이기 때문이다.

이러한 과정을 통해서 비동기 요청이 이루어진다.

이러한 비동기 방식의 문제점이 있다.

function findUser(id){
    let user;
    setTimeout(function (){
        user = {
            id: id,
            name: "User" + id,
            email: id + "@test.com"
        }
    },1000);
    return user;
}

const user = findUser(3);
console.log("user: ", user);   //user: undefined
//결과
user: undefined

setTimeout은 비동기 함수여서 console.log(user)가 실행될때 아직 setTimeout함수가 완료되지 못해서 아무값도 할당 받지 못한것이다.

이러한 비동기 방식으로 처리되는 코드들의 문제점을 해결하는 방법을 비동기 처리라고 한다.
비동기 작업의 순서를 보장해주는, 즉 동기적으로 처리될 수 있게 해주는 방법 3가지가 있다.

Callback

첫번째 방법은 콜백 함수를 이용하는 것이다.
함수의 결과값을 바로 리턴하는 것이 아니라, 결과값을 콜백함수로 넘겨 콜백 함수의 로직을 수행한다.

function findUser(id, callback) {
    let user;
    setTimeout(function () {
        user = {
            id: id,
            name: "User" + id,
            email: id + "@test.com",
        }
        callback(user)
    }, 1000);
}

findUser(3, function (user) {
    console.log("user:", user)
});
//결과
user: {id: 3, name: "User3", email: "3@test.com"}

콜백 지옥

콜백 함수만을 이용해서 비동기 처리를 할 경우 콜백 함수안에 콜백함수를 중첩하다보면
코드가 길어지고 지저분해지는데 이를 콜백 지옥이라고 부른다.

//예시
$.get('http://www.example.org', function(response) {
  task(response, function(result) {
    someTask(result, function(result) {
      someNextTask(result, function(result) {
        someNextNextTask(result, function(result) {
          ...
        })
      })
    })
  });
})

콜백 함수만을 이용하면 가독성,에러 처리 등에서 어려움을 겪게된다.
그래서 등장하는 것이 Promise이다.

Promise

콜백함수를 사용하면 매개변수를 콜백함수고 계속해서 전달해야 하지만
Promise는 Promise 객체를 생성하고 그 값을 리턴한다.
그 객체에서 결과값(성공결과 or 실패결과)을 가지고 .then 메서드의 인지로 넘겨주면된다.
위에 콜백 함수로 작성했던 코드를 promise 객체를 이용해서 작성하면

function findUser(id) {
  let user;
  return new Promise((resolve, reject) => {
    setTimeout(function () {
            user = {
                id: id,
                name: "User" + id,
                email: id + "@test.com",
            }
            resolve(user)
        }, 1000);
    });
}

findUser(3).then(function (result){
    console.log("user: ", result);
});

// user: {id: 3, name: "User3", email: "3@test.com"}

new Promise 를 통해서 promise를 생성하고 이 생성자는 executor라는 싱행함수를 인자로 받는다고 한다.
executor 함수는 resolve, reject 라는 2개의 함수형 매개변수를 가진다.
executor 내부에서 비동기적으로 코드가 실행되고 그 결과가 성공했을경우
그 성공한 값을 반환하는 resolve함수가 호출되고, 실패했을경우 에러처리를 하는 reject함수가 호출이 된다.

위 코드에서는 resolve로 반환된 결과가 then메소드에 의해서 처리되었지만
오류결과를 반환하는 reject의 반환값은 catch 메소드에 의해서 처리된다.

//ex
findUser(3).then(function (result){
    console.log(result);
}).catch(function (err) {
    console.log(err);
})

promise의 3가지 상태

프로미스 객체를 생성하고 종료될 때 까지 3가지의 상태가 있다.

  • Pending (대기) : 비동기로 처리되어야할 로직이 아직 완료되지 않은 상태
  • Fulfilled (이행) : 비동기 로직이 정상적으로 처리가 완료된 상태 (resolve로 반환)
  • Rejected (실패) : 비동기 로직의 처리가 실패 (reject로 반환)

async / await

async 와 await 은 프로미스 객체의 개념과 같고 코드를 간결하고
딱 보기에 순서대로(동기적으로) 진행되는 것처럼 보이게 하는 syntatic sugar이다.

프로미스도 체이닝으로 반복하다보면 콜백지옥처럼 난잡해지는 경우가 발생한다.
그 점을 보완하기 위해 async/await이 등장했다.

async

function myFunc() {
 return "myFunc";
}

async function myAsync() {
return "myAsync";
}

console.log(myFunc()); // myFunc
console.log(myAsync()); // Promise {<fulfilled>: "myAsync"}

위에 동일한 함수에 두번째 함수 앞에는 async를 붙여준 결과
promise 객체를 리턴하는 걸 볼수 있다.

따라서 async를 붙여주면 new Poromise 메소드를 사용하는것과 동일하다는 것을 알수 있다.

그렇다면 promise 객체에서 사용된 .then 메소드는 어떻게 사용될까

await

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("완료!"), 1000)
  });

  let result = await promise; // 프라미스가 이행될 때까지 기다림 (*)

  alert(result); // "완료!"
}

f();

promise에서 then 메소드가 promise의 객체가 반환될때까지 기다렸다가 그 리턴 값을 받아서 그 다음 코드로 넘어갔듯이, await을 이용해서 단어 뜻 그대로 그 값이 반환될때 까지 기다렸다가 다음 코드를 수행하게 한다.

async & await 예외 처리

try / catch 문법을 이용한다.

async function logTodoTitle() {
  try {
    var user = await fetchUser();
    if (user.id === 1) {
      var todo = await fetchTodo();
      console.log(todo.title); // delectus aut autem
    }
  } catch (error) {
    console.log(error);
  }
}

promise 와 동일하게 오류가 난 부분을 catch를 이용해서 처리해주었는데
async/await 에서는 정상적인 코드와 오류코드를 두가지 경우의 수로 처음부터 나눠서 진행한다.
위 코드와 같이 try / catch 문법을 활용해서 예외처리를 한다.

참고사이트
https://www.daleseo.com/js-async-callback/
https://velog.io/@minidoo/자바스크립트-콜백-함수Callback-Function
https://velog.io/@elena_park/JavaScript-비동기처리와-asyncawait
https://joshua1988.github.io/web-development/javascript/js-async-await/
https://meetup.toast.com/posts/89

0개의 댓글