비동기 1 : callback, promise , async & await

0

JAVASCRIPT

목록 보기
12/19
post-thumbnail

혹시나 잘못된 개념 전달이 있다면 댓글 부탁드립니다. 저의 성장의 도움이 됩니다

오늘의 Checkpoint

비동기 호출

Asynchronous call
효율성을 위해 작업 요청과 작업 실행이 분리되어 진행하는 상태
작업 요청이 블로킹되지 않는 상태
-> 하나의 작업이 끝나지 않아도 다음 작업이 진행(동시다발적으로 처리)
-> 완료에 도달하는 시간이 더 적음

  • non-blocking
    -> 요청 시점 ≠ 응답 시점
    -> 이벤트가 발생하면 콜백 수행

cf. blocking
: 하나의 작업이 완전히 끝날 때까지 다른 작업을 수행하지 못하는 현상
-> 요청시 즉시 응답(실행) = 동기

동기 Synchronous
: 블록킹으로 인해, 한 작업이 완료될 때 다른 작업이 시작하는 상태
-> 완료시적 = 시작 시점

cf. 비동기가 항상 좋은 방법은 아님 -> 상황에 따라 동기, 비동기 적용

콜백 함수
다른 함수의 전달인자로 넘겨지는 함수
-> 전달받은 함수는 콜백 함수를 필요에 따라 바로 실행하거나 나중에 실행할 수 있음
-> 이벤트에 의해 나중에 실행하면 비동기(바로 실행하면 동기)

  • 함수를 할당할 때 함수 호출이 아닌 함수 자체를 할당(실행값 X)
function handleClick(){
  console.log('버튼이 눌렸습니다!');}); 

document.querySeletor('#btn').onclick = handleClick;
document.querySeletor('#btn').onclick = function(){ handleClick(); }
document.querySeletor('#btn').onclick = handleClick.bind();
/* 
document.querySeletor('#btn').onclick = handleClick();
이 코드는 실행값을 전달한 것으로 잘못된 방법 -> () 없이 사용해야함
이 경우 undefined를 할당한 것
*/

동기 vs 비동기 방식

동기작업 코드(블록킹) 예시
: 주문 후 바로 메뉴를 만들어서 전달 후 다음 주문 받음
-> 시간이 오래걸림

// 현재와 시작 시간 비교하여 ms 범위 일때 무한 루프 도는 블로킹 함수
function waitSync(ms){
  let start = Date.now();
  let now = start;
  while(now-start < ms){
    now = Date.now();
  }
}

function getMenu(person, coffee){
  console.log(`${person} 고객님 주문하신 ${coffee}나왔습니다.`);
}
function orderCoffeeSync(coffee){
  console.log(`${coffee} 주문이 접수되었습니다.`);
  waitSync(5000);
  return coffee;
}

const orderLists = [
  {name: "김oo", orderMenu: '아이스라떼'},
  {name: "이oo", orderMenu: '자바프라프치노'},
  {name: "최oo", orderMenu: '아이스티'},
];

orderLists.forEach(function(orderLists){
  const menu = orderCoffeeSync(orderLists.orderMenu);
  getMenu(orderLists.name, menu);
})

비동기작업 코드(논블록킹) 예시
: 주문을 전부 받고, 메뉴가 완성되면 전달
-> 콜백 패턴 사용

function waitAsync(callback, ms){
  setTimeout(callback, ms); // 특정 시간 이후 함수 실행(브라우저 내장기능)
};

function getMenu(person, coffee){
  console.log(`${person} 고객님 주문하신 ${coffee}나왔습니다.`);
}
function orderCoffeeAsync(coffee, callback){
  console.log(`${coffee} 주문이 접수되었습니다.`);
  waitAsync( function(){ callback(coffee); }, 5000 );
  return coffee;
}

const orderLists = [
  {name: "김oo", orderMenu: '아이스라떼'},
  {name: "이oo", orderMenu: '자바프라프치노'},
  {name: "최oo", orderMenu: '아이스티'},
];

orderLists.forEach(function(orderLists){
  const callback = function(orderMenu){
    getMenu(orderLists.name, orderMenu);
  }
  const menu = orderCoffeeAsync(orderLists.orderMenu, callback);
})

비동기의 함수 전달 패턴 2가지


비동기 예시

  • DOM의 이벤트 핸들러
  • 타이머
  • 서버에 요청 및 응답
    : 클라이언트에서 요청을 보내면 서버의 응답을 받기 전에는 다른 작업 수행하고 응답을 받으면 그때 화면에 표시
    ex. 유투브에서 동영산이 로딩되는 동안 댓글, 추천 클립 등 영역의 화면은 동작 가능


Callback

비동기에서 순서를 제어하는 수단
: 동기 작업은 요청 순서대로 진행되지만, 비동기 작업에서는 먼저 끝나는 작업에 따라 응답 순서가 달라지므로 예상과는 진행순서가 달라질 수 있음
-> 콜백을 사용하여 진행 순서 핸들링

const doingWork = function(work, callback){
  const delay = Math.floor(Math.random()*100)+1
  const func = () => {
    console.log(`${work} 예상 시간은 ${delay}분 걸린다`); 
    callback();
  };
  setTimeout(func,delay);
};  

const housework = () =>{
	doingWork("빨래하기",()=>{
      doingWork("설거지하기",() =>{
        doingWork("빨래 널기",() =>{})
      })      
    })
}

housework();
// 각 작업의 소요 시간은 다르지만 원하는 순서대로 진행하여 총 작업시간을 줄일 수 있음
// cf. 만약 doingWork 정의할때 콜백을 전달하지 않았다면 efficientHousework에서 같은 순서로 실행하더라도 빠른 순서대로 출력됨


콜백헬
: 콜백으로 순서를 핸들링할 수 있지만 수정할 때 여러가지 오류가 발생할 위험 높음

  • 가독성 저하
  • 유지보수에 비효율적
    ex. 빨래널기, 이후 오늘 할일을 추가했을 때, 마트가 일찍 문닫아서 화분에 물주기 전에 마트갔다오고 순서대로 진행하기로 함 -> 수정하다가 괄호 빠뜨려서 오류 발생

Callback error handling

콜백함수를 사용하여 에러 처리하는 방식
-> 에러가 발생하는 상황과 에러가 없을 경우를 나눠서 콜백을 리턴

/* 예시를 위해 node.js 사용 */
const fs = require("fs");

const getDataFromFile = function (filePath, callback) {
  // readFile의 3번째 전달인자로 콜백을 전달해야함
  // 에러가 발생한 경우에 콜백의 전달인자에 에러를 넣고 데이터를 null 처리
  fs.readFile(filePath,'utf8', (err,data) => {
      if(err){
        return callback(err,null)
      } else{
        return callback(null,data)
      }
    }
  );
};

getDataFromFile('README.md', (err, data) => console.log(data));


Promise

콜백헬이 생기는 것을 방지하기 위해 콜백을 핸들링하는 방법
: 프로미스를 반환하는 함수에 프로미스 메서드를 나열하여 순서 지정
-> .then() 등 추가하여 다음 작업 순서 지정
-> 콜백 방식 보다 가독성 좋음

  • 리턴값으로 프로미스의 인스턴스
    new Promise(executor); 
    // 인스턴스의 전달인자로 콜백함수 사용
    // 콜백함수 전달인자 2개 : resolve, reject
    // (resolve,reject) => { // 콜백함수방식의 내용 + 콜백호출을 resolve 호출로 변경 등 }

Promise 메서드

실행1()
.then(()=>{return 실행2()})
.then(()=>{return 실행3()})
.catch(/* callback */)
.finally(/* 실패해도 수행 */)

// 실행1은 프로미스를 리턴하는 함수일 때

메서드의 실행 결과로 Promise를 반환
-> Promise chaining 가능

  • then(callback) : executor 실행 성공시 실행할 다음 작업 지정
    -> 프로미스 헬 방지
  • catch(callback) : 오류 발생시 실행할 작업 지정
    cf. 여러개의 then 이후에 한번 사용 가능
    : 콜백 방식에서는 내부에서 각 행동의 오류에 대한 핸들링을 지정해야함
  • finally(callback) : 실행의 완료 / 실패 여부에 상관없이 항상 수행되는 부분
    -> .catch() 이후 가장 마지막에 사용

Promise 상태

  • 대기 pendding
    : 완료되지도 오류가 발생하지도 않은 작업중인 상태
    cf. Promise() 내부에 resolve()reject() 가 사용되지 않은 상태도 포함
  • 이행 fulfilled / 거부 rejected
    : 성공적으로 완료 혹은 오류 발생
const rejectTest = (message) => {
  return new Promise((resolve, reject) =>{
    setTimeout(()=>{
      reject(new Error(message));
    }, 0)
  })
}
rejectTest('오류발생');
// rejected 상태
rejectTest('오류발생').catch(err => console.log(err));
// fulfilled 상태


Executor 매개변수

프로미스 실행함수의 parameter는 resolve, reject 2개
성공/ 실패 여부에 따라 둘 중 하나만 실행됨

  • resolve() : 성공적으로 실행될 경우 -> 리턴값은 .then() 의 전달인자로 전달
  • reject() : 실행이 거부되거나 에러 발생할 때 핸들링 -> 리턴값은 .catch() 의 전달인자로 전달

전달인자
Executor 매개변수가 함수로 사용될 때, 전달인자로 입력 받은 것이 연결된 프로미스 메서드의 전달인자로 전달됨
: resolve("next") 처럼 "next"를 전달하면 연결된 메서드 .then() 전달인자로 사용됨


콜백 방식과 비교

const doingWork = function(work){
  const delay = Math.floor(Math.random()*100)+1
  return new Promise((resolve, reject) => {setTimeout(() => {
    console.log(`${work} 예상 시간은 ${delay}분 걸린다`); 
    resolve();
  },delay)})
};  

const promiseHousework = () =>{
	doingWork("빨래하기")
      .then(()=> {return doingWork("설거지하기")})
      .then(()=> {return doingWork("빨래 널기")})
}

promiseHousework(); 
// doingWork 내부에서 오류가 발생하면 try,catch 방식 + reject() 추가하여 오류 핸들링


Promise chaining

const promise = new Promise(/*생략*/)

promise.then().then().then().then()

Promise를 반환할 경우 계속 메서드를 이어나갈 수 있는 것
-> 프로미스 메서드는 Promise를 반환하므로 프로미스 메서드를 계속 이어서 사용할 수 있음

  • 전달인자로 들어가는 함수의 return값이 연속된 메서드의 전달인자로 사용
    -> 리턴값이 없으면 undefined 가 다음 메서드로 전달




프로미스 헬
: 프로미스 방식으로 선언하더라도 then() 의 콜백함수에 return을 적용하지 않을때 발생
-> then() 의 콜백에서 return 의 중요성



Async & Await

프로미스 방식의 Syntactic sugar
비동기 방식으로 작동하지만 동기적으로 보이게 표현

  • async 함수 : 내부에서 순서를 정하는 함수 앞에 async 를 추가
  • await 키워드

cf. Syntactic sugar(문법적 설탕)
: 인간이 더 쉽게 이해하고 편하게 사용할 수 있도록, 간결하게 표현하거나 실제 동작과는 다른 방식으로 표현하는 구문을 의미
-> 코드의 가독성을 높이기 위한 방식

await 키워드

Promise를 반환하는 함수 앞에 사용하는 키워드

  • async 함수 내부에서만 사용
  • await 를 적용한 함수는 리턴값을 반환
    -> Promise를 반환 X
    cf. 프로미스 메서드는 프로미스를 반환

프로미스 방식과 비교

const doingWork = function(work){
  const delay = Math.floor(Math.random()*100)+1
  return new Promise((resolve, reject) => {setTimeout(() => {
    console.log(`${work} 예상 시간은 ${delay}분 걸린다`); 
    resolve();
  },delay)})
};  

const promiseHousework = async () =>{
  await doingWork("빨래하기")
  await doingWork("설거지하기");
  await doingWork("빨래 널기");
}

promiseHousework();
// Promise 방식 기반의 표현
// Promise 메서드 대신 리턴값을 활용하여 동기 방식처럼 표현
// resolve()의 전달인자나 함수의 return 값을 반환
// -> 프로미스를 리턴 X

비동기 방식 가독성
async, await 방식 > promise 방식 >>> callback 방식



타이머 API

브라우저에서 제공하는 web API

setTimeout()

지정한 시간 뒤에 콜백함수를 실행

  • 비동기 함수
    -> 코드 전체가 실행되고 stack이 비어있으면 콜백 함수가 실행
  • 리턴값 : 자신을 가리키는 ID(양의 정수)
  • clearTimeout() 으로 취소 가능
setTimeout(callback, delay, arg1,...,argN);
// delay : millisecond 단위로 callback함수를 해당값만큼 지연 후 실행(타이머)
// 		  -> 1000 입력시 1초 뒤 실행
// arg1, ..., argN : callback의 arguments로 필요한 경우만 입력
const leaveMessage = () => { console.log("3초 뒤 실행"); };

setTimeout(leaveMessage, 3000);
// ID 반환 후 3초 뒤 메세지 남음

clearTimeout()

setTimeout 타이머 취소

  • 리턴값 X
clearTimeout(timeoutID);
// timeoutID : 취소할 대상의 ID
// 			  -> setTimeout실행값을 변수에 담아 전달인자로 넘김(setTimeout의 리턴값)
const timerID = setTimeout(leaveMessage, 3000);
clearTimeout(timerID);


setInterval()

지정한 시간 간격으로 콜백함수를 반복적으로 수행

  • 비동기 함수
    -> 코드 전체가 실행되고 stack이 비어있으면 콜백 함수가 실행
  • 리턴값 : 자신을 가리키는 ID(양의 정수)
  • clearInterval() 로 취소 가능
setInterval(callback, delay, arg1,...,argN);
// delay : millisecond 단위로 callback함수를 해당값만큼 지연 후 실행 반복
// 		  -> 1000 입력시 1초 뒤 실행
// arg1, ..., argN : callback의 arguments로 필요한 경우만 입력
const intervalID = setInterval(leaveMessage, 3000);

clearInterval()

setInterval 타이머 취소

  • 리턴값 X
clearInterval(intervalID);
// intervalID : 취소할 대상의 ID
//             -> setInterval실행값을 변수에 담아 전달인자로 넘김(setInterval의 리턴값)
const intervalID = setInterval(leaveMessage);
clearInterval(intervalID);



참고

얕은 복사 후 새 배열 반환
-> immutable
-> const result = []; -> 동작 실행 -> return result;




오늘의 나

느낀점
라이브러리 만들어보는 과제 너무 어려웠다. reduce()같은 고차 함수는 어느정도 이해했다고 생각했는데 아니었다. 물론 그 메서드들의 내용을 구현하는거라 그 함수 자체를 이해못한건 아니지만, 확실하게 콜백함수에서 함수의 변수를 어떻게 적용시켜야하는지는 아직 미숙한 것 같다. 함수 호출이 아닌 정의할 때는 자유롭게 사용할 수 있지만 이게 은근 헷갈린다. 특히 _.each 메서드 구현하는게 이해가 안간다. iteratee(ele, idx, arr) 이 부분.. 그냥 문제에서 하라는 규칙인건가.. 혼란스럽다. 어드벤스드 문제는 커녕 베어미니멈 문제도 완전히 아는 것 같지는 않다. 어드벤스드를 도전하는건 필기 정리, 못한 부분 다 하고 해야겠다. 이러다 할 수 있나...?? 오늘도 금요일이라 css 연습 해보려고했는데... 또 과제하느라 12시다. 이럴땐 쉬운 문제 풀이로 힘을 받아야지!!

개선점 및 리마인드

초조해하지 말자!

오늘 특히 초조해진 것 같다. 불안해하지 말자!!

**

0개의 댓글