TIL 2020-12-21 (비동기, Promise)

nyongho·2020년 12월 21일
0

JavaScript

목록 보기
18/23

Asynchronous & Promise


TIL List

  • 동기, 비동기의 차이점
  • Promise 의 개념 및 메소드
  • async 와 await
  • callback, promise, async await 의 차이점

1) Synchronus

동기적인 처리 방법

시작하기 전 먼저 다음의 단어들을 이해하고 넘어가자.

Client (클라이언트)Server (서버)
번역하면 '서비스를 의뢰하는 사람' 이라는 기의를 가지고 있다. 이는 프로그래밍에서 "서버에게 데이터를 요구하는 것" 라는 뜻으로 쓰여진다.클라이언트에게 네트워크를 통해 서비스하는 컴퓨터를 의미한다.
EX) 내 컴퓨터EX) 웹 서버, 게임 서버

클라이언트가 서버에서 데이터를 받아올 때 받아올 것이 4개로 가정한다면 동기적인 처리 방법은 다음과 같이 데이터를 처리할 것이다.

DATA일 순서소요시간 (sec)
AA -> B6
BB-> C11
CC -> D21
DA->B->C->D29

🖥 위와 같이 A -> B -> C -> D 순으로 일을 처리한다.

따라서 총 소요시간 : 29초

Asynchronous

비동기적인 처리 방법

위 예시와 조건 동일

DATA일 순서소요시간 (sec)
AA6
BB5
CC10
DD8

모든 것을 동시에 처리하므로 총 소요시간은 가장 긴 시간인 C 가 총 소요시간이 된다.

따라서 총 소요시간 : 10초

유튜브 영상 로딩될 때 동기적이면 해당 영상이 로딩될 때까지 댓글창도 못쓰고 추천 영상도 못 보게 된다.
하지만 비동기적이면 유튜브 영상 하나가 로딩될 때 먼저 비동기적으로 처리 된 댓글창도 쓱 볼 수 있고 옆에 떠 있는 추천 영상도 확인 할 수도 있게 된다.

2) Async 가 Sync 보다 더 효율적인 건 알겠는데 그 처리 순서를 제어하고 싶으면?

예를 들어 내가 A,B,C 함수를 순서대로 넣었고 이 순서대로 일 처리를 했으면 좋겠다고 생각했다. 근데 우리가 이전에서 배웠던 해당 함수의 시간복잡도나 서버에 데이터를 보낼 때 걸리는 시간 등 다양한 요소 때문에 우리가 원하는 순서대로 일 처리가 안되고 B,C,A 순으로 처리될 수도 있다.

2-1. 콜백의 이용하라!

CALLBACK 은 영문권에서 "야 너 그거 끝나면 나한테 다시 전화해" 라는 뜻으로, 실생활에서 많이 쓰는 단어
ex) Can you give me a call back?

각 함수 A,B,C 를

A() => {
  B() => {
    C() => {
    
    }
  }
}

이런식으로 콜백으로 넣게 되면

A를 실행해서 B 를 실행하고 B 를 실행한다음에 C를 실행하게끔 우리가 명시적으로 짜줬기 때문에 우리가 원하던 A->B->C 순으로 일을 처리할 수 있게 된다!

2-2. 콜백은 좋긴 하지만..

만약 함수가 A,B,C 뿐만 아니라 한 50개 정도를 순서대로 처리해야 한다고 가정하면

그 코드의 가독성은 매우 떨어질 것이다.

(영문권에서는 이것을 callback Hell 이라고 표현하나보다)

따라서 우리는 아래의 개념을 배워야 한다.

3) Promise

프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다. 다만 최종 결과를 반환하지는 않고, 대신 프로미스를 반환해서 미래의 어떤 시점에 결과를 제공합니다.

Promise 는 다음 중 하나의 상태를 가지게 된다.

  • 대기(pending): 이행하거나 거부되지 않은 초기 상태.

  • 이행(fulfilled): 연산이 성공적으로 완료됨.

  • 거부(rejected): 연산이 실패함.

Promise 라는 인스턴스는 아래의 형태로 이루어져있다.

new Promise((resolve, reject) => {})

3-1 .resolve()

주어진 값으로 이행하는 Promise 객체를 반환한다.

resolve() 가 실행되었을 때 3가지 경우

  1. 뒤에 then 이 존재하는 경우
  • then 메서드를 따라가고 마지막 상태를 취한다.
  1. 그렇지 않은 경우

    • 주어진 값으로 이행한다.
  2. 프로미스인지 알 수 없는 경우

    • Promise.resolve(value) 후 반환값을 프로미스로 처리할 수 있다.

3-2 .reject()

주어진 이유로 거부하는 Promise 객체를 반환한다.


3-3 .then()

then() 메서드는 Promise를 리턴하고 두 개의 콜백 함수를 인수로 받습니다. 하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수입니다.

// resolve 실행 됐을때!

const promise1 = new Promise((resolve, reject) => {
  resolve('Success!');
});

promise1.then((value) => {
  console.log(value); // expected output: "Success!"
});


// reject 실행 됐을 때!

const promise1 = new Promise((resolve, reject) => {
  reject('Failed!');
});

promise1.then((value) => {
  console.log(value); 
}, (reason) => {
  console.log(reason) // Failed!
});

3-4 .catch()

catch() 메서드는 Promise 를 리턴하고 rejected 된 값 만을 출력합니다.

프로미스 에러 처리는 .then().catch() 메서드가 있다.

function getData() {
  return new Promise(function(resolve, reject) {
    reject('failed');
  });
}

// 1. then()의 두 번째 인자로 에러를 처리하는 코드
getData().then(function() {
  // ...
}, function(err) {
  console.log(err);
});

// 2. catch()로 에러를 처리하는 코드
getData().then().catch(function(err) {
  console.log(err);
});

하지만 프로미스 에러 처리는 가급적 .catch() 를 사용하는 것을 권장하는데, 그 이유는 다음 코드를 보면 알 수 있다.

function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result);
  throw new Error("Error in then()"); // Uncaught (in promise) Error: Error in then()
}, function(err) {
  console.log('then error : ', err);
});

위와 같이 첫번째 콜백 함수 내부에서 에러가 나는 경우 제대로 처리하지 못한다.

다음은 catch() 를 사용했을 때이다.

// catch()로 오류를 감지하는 코드
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}

getData().then(function(result) {
  console.log(result); // hi
  throw new Error("Error in then()");
}).catch(function(err) {
  console.log('then error : ', err); // then error :  Error: Error in then()
});

위와 다르게 발생한 에러를 잘 출력한 모습이다.


3-5 .all()

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부합니다.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: Array [3, 42, "foo"]

출처 : MDN


3-6. Promise Hell 도 존재한다!

// nested Promise
// Callback Hell 이 싫어서 Promise 를 도입했더니 이런 결과가...!!! 😱

A()
.then(data => {
    console.log(data)

    B()
    .then(data => {
        console.log(data)

        C()
        .then(data => {
            console.log(data)
            
            D()
            .then(data => {
                console.log(data)
   
            })
        })
    })
})

위의 해결방법은 다음과 같다.

  1. 일의 순서를 확실하게 하기 위해 스코프를 확실하게 정한다.

  2. 다음 함수를 return 하게 한다.

// Promise Chain

A()
.then(data => {
    console.log(data)
    return B()
})
.then(data => {
    console.log(data)
    return C()
})
.then(data => {
    console.log(data)
    return D()
})
.then(data => {
    console.log(data)
})

현재 실행되고 있는 문맥 다음에 .then() 메서드가 존재한다면 return 을 통해 현재 값을 다음 .then() 에 넘겨줄 수 있다.

이것을 Promise Chain 이라고 한다.

하지만 이러한 것만 보고서 무조건 "Promise Chain 이 옳다!!" 라고도 할 수 없다.

가장 적절한 방법은 상황에 따라 nested PromisePromise Chain 을 적절히 섞어가며 사용하는 것이라고 할 수 있다.


4) 더 이상 고민 말고 쓰자! async & await

async & await 라는 특별한 문법을 사용하면 Promise 를 좀 더 편하게 사용할 수 있다. async & await 은 굉장히 이해하기 쉽고, 사용법도 어렵지 않다.

async 를 사용하려면 무조건 function 앞에 위치해야 한다.

async function A() {
  return 'hi';
}

4-1. async 사용 시 효과

  1. 해당 함수는 언제나 Promise 를 반환한다. (Promise 가 아니더라도)

  2. 함수 안에서 await 사용이 가능하다.

  3. promise.then, promise.catch 가 거의 필요 없게 된다. (무조건 안써도 된다는건 아님)


4-2. await 을 사용해보자

자바스크립트는 await 키워드를 만나면 해당 Promise 가 처리될 때까지 기다린다.

async function A () {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("2초 뒤 뿅!"), 2000)
  });

  let result = await promise; // 프라미스가 이행될 때까지 기다린다. 즉, 2초간 기다림

  alert(result); // "2초 뒤 뿅!"
}

A();
profile
두 줄 소개

0개의 댓글