미가공 필기(JS)

JS 비동기 처리

JoJobum 2022. 5. 19.

Js 언어상의 특징으로 싱글 쓰레드를 사용 => 한번에 한가지 일만 처리 가능

이러한 점에 대한 단점들을 보완하기 위해 언어 자체는 병렬 처리를 못하지만,

Js를 읽고 실행하는 엔진에서 병렬처리를 하는 구조

 

비동기란? 

A, B, C 실행시킬 때

동기: A 끝날 때까지 기다리고 B 실행, B 끝날 때까지 기다리고 C 실행

비동기: 그냥 각자 A,B,C 동시 실행

비유하자면 동기: 릴레이 계주 , 비동기 : 100m 달리기

 

이벤트 기반 동작을 코드로 구현한 방법

 

1. Callback : 전통적인 Javascript의 이벤트 기반 코딩 방식

2. Promise : Callback의 단점(= Callback 지옥 등...) 보완한 방식

3. Async - Await : Promise의 단점 보완한 방식

 

1. Callback

function countDown(count, callback) {
    console.log(count);
    
    //count ==0 이면 앞서 받아놨던 callback 실행하고(BOOM! 출력) return 
    if (count === 0) {
        callback();
        return;
    }
    //count 줄여나가며 재귀
    setTimeout(() => {
        countDown(count - 1, callback);
    }, 1000);
}

//count == 5, callback 메소드 내용 == BOOM! 출력
countDown(5, ()=>{
    console.log("BOOM!");
});

Callback 지옥

function add3(a, callback) {
    setTimeout(() => callback(a + 3), 100)
    // 100ms가 지난 후 함수로 입력받은 callback에 a + 10값을 다시 입력하여 callback함수 호출
}

add3(10, res => {
    console.log(res) // 13 
    add3(res, res => {
        console.log(res) // 16
        add3(res, res => {
            console.log(res) //19
            add3(res, res => {
                console.log(res) //22
            })
        })
    })
})

함수 재귀적으로 호출하기에 가독성 떨어짐

 

 

 

2. Promise

promise의 기본 컨셉은 현재는 당장 없을 수 없지만, (보통 데이터를 얻는 데까지 지연이 발생하는 경우)

근미래에 얻을 수 있는 데이터에 접근하기 위한 방법.

promise 의 필요성: 지연이 발생한 경우는 주로 I/O 나 Network를 통해서 데이터를 얻는 과정에서 발생하는데 이는 cpu 입장에서 매우 긴 시간, 앞서 말했듯이 js 는 싱글 쓰레드이기에 이를 기다려 줄 수 없음 (Non-blocking 코드 지향)

 

Promise는 resolve, reject 라는 2개의 함수형 파라미터를 가짐

//Promise 객체 생성하여 변수에 할당
const promise = new Promise(function(resolve, reject) { ... } );
//보통은 어떤 함수의 리턴 값으로 바로 사용된는 경우 많음
function returnPromise() {
  return new Promise((resolve, reject) => { ... } );
}

resolve() 는 미래 시점에 얻게 될 결과를 넘겨주고

reject() 는 미래 시점에 발생할 예외를 넘겨줌

 

예시 - 직접 Promise 객체 생성해서 사용하는 경우

function devide(numA, numB) {
  return new Promise((resolve, reject) => {
    if (numB === 0) reject(new Error("Unable to devide by 0."));
    else resolve(numA / numB);
  });
}

devide(8, 2)
  .then((result) => console.log("성공:", result))
  .catch((error) => console.log("실패:", error));
// 실행 결과=> 성공: 4 
// resolve(numA / numB) 를 타고 .then((result) => 를 타고 출력된 결과

devide(8, 0)
  .then((result) => console.log("성공:", result))
  .catch((error) => console.log("실패:", error));
// 실행 결과=> 실패: Error: Unable to devide by 0.
// if 문의 numB===0에 걸려 (예외) reject 타고 .catch를 타고 출력된 결과

 

예시2 - 어떤 라이브러리의 함수 호출시 리턴 받는 Promise 객체 사용하는 경우

// 어떤 서비스의 API 호출시 응답 결과에 따라 결과/예외 처리  
// REST API 호출 시 사용되는 fetch()는 URL 인자로 받고 호출 결과를 Promise 객체로 리턴
fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => console.log("response:", response))
  .catch((error) => console.log("error:", error));

// resolve된 경우: response: Response { 결과 ~~~ }
// reject된 경우: error: TypeError: Failed to execute 'fetch' on 'Window':~~~

 

메서드 체이닝(Method Chaining)

 

then(), catch() 메서드는 또 다른 Promise 객체를 리턴하고,

이 객체는 인자로 넘긴 콜백 함수의 리턴값을 다시 then()과 catch() 를 통해 접근할 수 있게 해줌

 

예를 들어, fetch()에서 단순히 응답 결과만이 아니라

응답 전문을 json 형태로 출력하고 싶은 경우에는 then() 메서드를 추가로 연결해주면 된다.

fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((post) => console.log("post:", post))
  .catch((error) => console.log("error:", error));

 

Promise의 문제점

 

동일한 이름의 메서드인 then()을 연쇄적으로 호출하고 있어서 도대체 몇 번째 then()에서 문제가 발생한 건지 Stack Trace을 봐도 알기 어려울 수 있음

 

3. Async - Await 

 

비동기 코드를 동기 코드처럼 작성할 수 있음

async 키워드는 함수, function 앞에 붙는다. 

await 키워드는 함수 내부에서만 사용할 수 있고, 비동기 함수가 리턴하는 Promise에서 결과값을 추출한다.

그 말은 바로 await 키워드를 사용하면 바로 다음 라인으로 넘어가는 것이 아닌, 말 그대로 await! 기다려! 준다. (결과값을 얻을 때까지) 

function adder_promise(a, b) {
    return new Promise((resolve, reject) => {
        resolve(a+b);
    });
}

function main_promise(a, b, c, d) {
    Promise.all([
        adder_promise(a, b),
        adder_promise(c, d),
    ])
    .then(([r1, r2]) => {
        return adder_promise(r1, r2);
    })
    .then((r3) => {
        console.log(`${a}+${b}+${c}+${d}=${r3}`);
    });
}

/* 1. main 을 async 함수로 선언 */
async function main(a, b, c, d) {
    const [r1, r2] = await Promise.all([
        adder_promise(a, b),
        adder_promise(c, d),
        ]);/* 2. 두 promise 함수를 동시에 실행하여 결과를 r1, r2에 저장 */
    const r3 = await adder_promise(r1, r2);/*3. r1 과 r2 를 한번 더 adder_promise 로 실행 */
    console.log(`${a}+${b}+${c}+${d}=${r3}`);
}

main(1,2,3,4);
// 실행 결과: 1+2+3+4=10

 

주의할 점: async 키워드가 붙어 있는 함수를 호출하면 명시적으로 Promise 객체를 생성해서 리턴하지 않아도 Promise  객체가 리턴된다. 

 

 

 

 

참고한 글

[자바스크립트] 비동기 처리 2부 - Promise | Engineering Blog by Dale Seo

[자바스크립트] 비동기 처리 3부 - async/await | Engineering Blog by Dale Seo

프라미스 (javascript.info)

async와 await (javascript.info)

반응형

'미가공 필기(JS)' 카테고리의 다른 글

[JS] Lexical Scope, Dynamic Scope  (0) 2022.08.08
Nest JS 공식문서 핥기(1)  (0) 2022.05.19

댓글