동기, 비동기란

MIN KYOUNG KIM·2022년 3월 14일
1

동기 vs 비동기란

동기는 순차적, 직력적으로 테스크를 수행한다.
비동기는 병렬적으로 테스크를 수행한다.

동기란?

  • 동기는 요청을 보낸 후 응답 (결과물)을 받아야지만 다음 동장이 이루어지는 방식을 말한다.
  • 모든 일은 순차적으로 실행되며 어떤 작업이 수행중이라면 다음 작업은 대기하게 된다.
    • 장점: 설계가 간단하고 직관적
    • 단점: 결과를 볼대 까지 아무것도 못하고 대기해야한다

비동기란?

  • 비동기는 동시에 일어나지 않는다 의미한다. 요청과 결과가 동시에 일어나지 않는 거라는 약속.
  • 요청한 그 자리에서 결과가 주어지지 않음
  • 노드 사이의 작업 처리 단위를 동시에 맞추지 않아도 된다.
    • 장점: 결과가 주어지는 데 시간이 걸리더라도 그동안 다른 작업이 가능해 자원의 효율적인 사용이 가능
    • 단점 : 설계가 동기보다 복잡함

아래의 사진을 보면서 동기와 비동기를 이해해보자!

  • 동기
    • 첫번째 손님이 아메리카노를 시켰다. 첫번 째 손님의 커피가 나올때 까지 2번째 손님은 기다려야한다.
    • 첫번째 손님이 요청한 아메리카노가 나온 뒤에 , 2번째 손님의 주문을 받을 수 있다.
    • 즉, 앞의 손님의 요청과 결과가 다 나올때까지 뒤의 손님은 계속 기다려야한다.
  • 비동기
    • 카운터에서 일단 첫번째 손님의 커피주문을 받는다. 첫번째 손님은 진동벨을 가지고 커피를 기다린다.
    • (첫번째 손님의 커피는 나오지 않았지만) 카운터에서 두번째 손님의 주문을 받는다.
    • 진동벨이 울리면, 첫번째 손님이 카운터로 와 커피를 받아간다.
    • 비동기 같은 경우, 일단 손님들의 요청을 계속 받으면서, 요청한 결과를 처리하는 한다.

출처: https://simju9397.tistory.com/45

그렇다면 비동기는 왜 필요할까?

만약 우리가 데이터를 서버에서 받아오는 앱을 만든다고 가정해보자.
먼저, 서버로부터 데이터를 받아오는 코드가 실행되어야 한다. 만약 비동기로 처리하지 않고, 동기적으로 구성을 하게 된다면, 데이터를 받아오기까지 기다린 다음에 앱이 실행될 것이다. 서버에 가져오는 데이터 양이 많을 수록 앱의 실행속도는 기하급수적으로 느려진다. 데이터를 불러오기까지 앱이 대기하는 상태가 발생된다. 이런 불편을 없애기 위해서 데이터를 수신하는 코드와 페이지를 표시하는 것은 비동기적으로 처리를 해야한다. 가장 대표적인 예가 setTimeOut과 AJax다.

출저: https://velog.io/@lsj8367/Javascript-동기와-비동기방식의-차이점

동기 처리 

console.log(1);
console.log(2);
console.log(3);

//1
//2
//3 
비동기 처리 

console.log(1);
setTimeout(() => {
	console.log(2)
},2000)
console.log(3);

//1
//3
//2

Callback

콜백함수란?

  • 이름 그대로 나중에 호출되는 함수를 말한다.

  • 특정함수에 매개변수로 전달된 함수를 의미한다.

  • 콜백 함수는 코드를 통해 명시적으로 호출하는 함수가 아니라, 개발자는 단지 함수를 등록 하기만 하고, 어떤 이벤트가 발생했거나 특정 시점에 도달했을 때 시스템에서 호출하는 함수를 말한다.

    콜백은 좀 더 쉽게 이해해보기 🔻

콜백 함수의 동작 방식은 일종의 식당 자리 예약과 같습니다. 일반적으로 맛집을 가면 사람이 많아 자리가 없습니다. 그래서 대기자 명단에 이름을 쓴 다음에 자리가 날 때까지 주변 식당을 돌아다니죠. 만약 식당에서 자리가 생기면 전화로 자리가 났다고 연락이 옵니다. 그 전화를 받는 시점이 여기서의 콜백 함수가 호출되는 시점과 같습니다. 손님 입장에서는 자리가 날 때까지 식당에서 기다리지 않고 근처 가게에서 잠깐 쇼핑을 할 수도 있고 아니면 다른 식당 자리를 알아볼 수도 있습니다.
자리가 났을 때만 연락이 오기 때문에 미리 가서 기다릴 필요도 없고, 직접 식당 안에 들어가서 자리가 비어 있는지 확인할 필요도 없습니다. 자리가 준비된 시점, 즉 데이터가 준비된 시점에서만 저희가 원하는 동작(자리에 앉는다, 특정 값을 출력한다 등)을 수행할 수 있습니다.
출저: 캡팁판교 비동기/동기

콜백 함수에는 동기,비동기가 있다.

  • synchronous callback
  • asynchronous callback
console.log(1);
setTimeout(() => {
	console.log(2)
},2000)
console.log(3);

// synchronous callback
function printImmediately(print){
	print()
}

printImmediately(() => console.log('hello'))

//Asynchrouns callback
function printWithDelay(print, timeout){
	setTimeout(print, timeout)
}

printWithDelay(() => console.log('async callback), 2000)

//1
//3
//'hello'
//'async callback' 
  • 자바스크립트 execute 했을 때 컴퓨터가 인식한 모습 (hoisiting )
    //hoisting 된모습 
    
    // synchronous callback
    function printImmediately(print){
    	print()
    }
    
    //Asynchrouns callback
    function printWithDelay(print, timeout){
    	setTimeout(print, timeout)
    }
    
    console.log(1);  ----> 동기
    setTimeout(() => {
    	console.log(2)
    },2000)          -----> 비동기 
    console.log(3);
    
    printImmediately(() => console.log('hello'))  ----> 동기 
    printWithDelay(() => console.log('async callback), 2000) ----> 비동기 
    

callBack 지옥

function Callback(callback){
    console.log('콜백 함수');
    callback();
}
Callback(() => {
    console.log('콜백을 받는곳!')
})
  • 가독성이 떨어짐
  • 비즈니스 로직을 한 눈에 보기 어려움
  • 에러가 발생했을때, 문제를 찾기 어려움
  • 유지보수가 어렵다

Promise

  • Promise is a JavaScript Object for asynchronous operation.
  • 프로미스는 자바스크립트 비동기 처리에 사용되는 객체이다. 비동기 처리를 제어하는, 혹은 기존 비동기 처리 코드들을 동일한 (표준) API 형태로 사용할 수 있게 해주는 ES6에 추가된 기능이다.
  • when a new Promise is created, the executor runs automatically.

Promise 객체 사용법

const getThreePromise = () => new Promise((resolve, reject) => {
	resolve(3);
});

getThreePromise().then(console.log);

Producer vs Consumer


//producer 
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("hello world!!!!");
    //  reject(new Error('no network'))
  }, 2000);
});

//consumer : then, catch, finally  
promise
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
    console.log("finally");
  });

state

  • Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태

  • Fulfilled(이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태

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

    Pending

아래와 같이 new Promise() 메서드를 호출하면 대기 상태가 된다.

new Promise()

Fullfilled -이행

콜백 함수의 인자 resolve를 아래와 같이 실행하면 이행 상태가 된다.

function getData(){
		return new Promise((resolve, reject) => {
		let data = 100;
		resolve(data);
	})
}

getData().then(data => console.log(data));

//100

rejected - 실패

function getData(){
   return new Promise((resolve,reject) => {
       reject(new Error('에러발생'))
   })
}

getData().then().catch(console.log)
//[object Error] {....}

출처 : 프로미스 흐름 처리

여러 개의 프로미스 연결하기 (Promise Chaining)

Promise Chaining

const fetchNumber = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
});

fetchNumber
  .then((num) => num * 2) //2
  .then((num) => num * 3) //6
  .then((num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(num - 1), 1000); //5 
    });
  })
  .then((num) => console.log(num)); //5

//5 

Error Handling

const getHen = () =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve("🐓"), 1000);
  });

const getEgg = (hen) =>
  new Promise((resolve, reject) => {
    // setTimeout(() => resolve(`${hen} => 🥚`), 1000);
    setTimeout(() => reject(new Error(`${hen} => 🥚`) ), 1000);
  });

const cook = (egg) =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${egg} => 🍳`));
  });

getHen() //
  .then(getEgg)
  .catch(error => {
      return '🥖'
  })
  .then(cook)
  .then(console.log)
  .catch(console.log);//🥖 => 🍳

callback → promise

callback 함수

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === "min" && password === "dream") ||
          (id === "coder" && password === "wecode")
        ) {
          resolve(id);
        } else {
          reject(new Error("not found"));
        }
      }, 2000);
    })
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "min") {
          resolve({ name: "min", role: "admin" });
        } else {
          reject(new Error("no access"));
        }
      }, 1000);
    });
  }
}

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your password");

userStorage.loginUser(id, password)
.then(userStorage.getRoles)
.then( user =>  alert(`Hello ${user.name}, you have a ${user.role} role`))
.catch(console.log)

promise 함수 바꾼 것

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === "min" && password === "dream") ||
          (id === "coder" && password === "wecode")
        ) {
          resolve(id);
        } else {
          reject(new Error("not found"));
        }
      }, 2000);
    })
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "min") {
          resolve({ name: "min", role: "admin" });
        } else {
          reject(new Error("no access"));
        }
      }, 1000);
    });
  }
}

const userStorage = new UserStorage();
const id = prompt("enter your id");
const password = prompt("enter your password");

userStorage.loginUser(id, password)
.then(userStorage.getRoles)
.then( user =>  alert(`Hello ${user.name}, you have a ${user.role} role`))
.catch(console.log)

async & await

async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다. 기존의 비동기 처리 방식인 콜백 함수와 프로미스의 단점을 보완하고 개발자가 읽기 좋은 코드를 작성할 수 있게 도와준다.

  • Syntactic Sugar란? 문법적인 기능은 그대로 유지하되, 코드를 작성하는 사람 입장에서 혹은 그 코드를 다시 읽는 사람의 입장에서 편의성이 높은 프로그래밍 문법을 의미합니다.

await 사용법

async function 함수명() {
  await 비동기_처리_메서드_명();
}

function fetchItems() {
  return new Promise(function(resolve, reject) {
    var items = [1,2,3];
    resolve(items)
  });
}

async function logItems() {
  var resultItems = await fetchItems();
  console.log(resultItems); 
}

logItems();

예시


function delay(ms){
    return new Promise((resolve) => setTimeout(resolve => ms))
}

// promise를 사용할때 
function getApple(){
    return delay(300)
    .then(() => console.log('🍎'))
}

// async 를 사용하면 아래와 같이 바꿀수 있다. 

async function getApple(){
   await delay(3000)
	 return '🍎'
}

getApple().then(console.log) // "🍎"
function delay(ms){
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function getApple(){
    await delay(3000);
    return '🍎'
}

async function getBanana(){
    await delay(3000);
    return '🍌'
}

function pickFruits(){
    return getApple()
    .then(apple => {
        return getBanana().then(banana => `${apple} + ${banana}`)
    })
}

pickFruits().then(console.log)

// '🍎+🍌'

그렇지만 promise 많이 쓰면 콜백지옥 같은 일이 발생한다. 

async function pickFruits(){
	 const apple = await getApple();
	 const banana = await getBanana();

		return `${apple} + ${banana}`
}

pickFruits().then(console.log)

// 바나나와 사과를 순차적으로 받아올 필요가 없다. 
// Promise가 생성되자마자, 실행된다는 점을 이용해보자  
async function pickFruits(){
	const applePromise = getApple();
	const bananaPromise = getBanana()
	const apple = await applePromise;
	const banana = await bananaPromise;
  
    return `${apple} + ${banana}`

}
pickFruits().then(console.log)

error handling (try catch)

function delay(ms){
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function getApple(){
    await delay(1000);
//     throw 'error가 발생함'; 
    return '🍎';
}

async function getBanana(){
    await delay(1000);
    return '🍌'
}

async function pickFruits(){
    try{
        const apple = await getApple();
        const banana = await getBanana();
        
       return `${apple} + ${banana}`

    }catch(err){
        console.log(err)
    }
}

pickFruits().then(console.log)

유용한 Promise API

Promise.all([ ])

function pickAllFruits() {
    return Promise.all([getApple(),getBanana()])
    .then(fruits => fruits.join(' + '))
}

pickAllFruits().then(console.log); //"🍎 + 🍌"

Promise.race([])

function delay(ms){
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function getApple(){
    await delay(2000);
    return '🍎'
}

async function getBanana(){
    await delay(1000);
    return '🍌'
}

function pickOnlyOne(){
    return Promise.race([getApple(),getBanana()])
}

pickOnlyOne().then(console.log) // 🍌 ---바나나만 뜸 

자바스크립트의 동작원리: 엔진: 런타임, 호출 스택

참고 블로그 :https://resilient-923.tistory.com/308

  • 자바스크립트의 큰 특징 중 하나는 '단일 스레드' 기반의 언어이다. 동시에 하나의 작업만 처리 할 수 있다.
  • but, 자바스크립트가 사용되는 환경을 생각해보면 많은 작업이 동시에 처리되고 있는 걸 볼 수 있다.

출처 : https://resilient-923.tistory.com/308

호출 스택 (call stack)

엔진의 주요 두 구성요소는

  • Memory Heap : 메모리 할당이 일어나는 곳
  • Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳

자바스크립트 엔진은 Memory Heap과 Call Stack으로 구성되어 있다.

자바스크립트는 단일 스레드(싱글 스레드) 프로그래밍 언어인데, 이 말은 Call Stack이 하나 라는 의미이다.

MemoryHeap은 우리가 프로그래밍을 할 때 선언된 변수, 함수 등이 담겨지는, 메모리 할당이 일어나는 곳이다.

Call Stack은 코드가 실행될 때 쌓이는 곳이고, Stack형태로 쌓이게 된다.

Stack은 LIFO(Last In First Out) 선입후출 즉, 가장 마지막에 들어온 게 먼저 나가는 구조이다.

Web API


  • ****Web API는 JS엔진이 아닌, 브라우저에서 제공하는 API이다. (대표적 setTimeout, AJax)
  • Call Stack에서 실행된 비동기 함수는 Web API를 호출하고, Web API는 콜백함수를 Callback Queue에 넣어준다.

Callback Queue / Task Queue

  • 비동기로 실행된 콜백 함수가 보관되는 영역이다.
  • Queue 자료구조는 FIFO(First In First Out) 구조로 섭입 선출, 먼저 들어간 게 먼저 나오는 구조이다.

Event Loop란?

  • Event Loop는 Call Stack과 Callback Queue의 상태를 체크하여,Call Stack이 빈 상태가 되면, Callback Queue의 첫번째 콜백을 Call Stack으로 밀어 넣는다
  • 이러한 반복적인 행동을 틱(tick) 이라 부른다.
profile
sin prisa pero sin pausa

0개의 댓글