JS - 10일차(비동기 프로그래밍)

김혜성·2021년 7월 16일
0

프론트엔드

목록 보기
13/17

동기


  • 순서대로 코드가 실행됨! === 동기식 프로그래밍

배달시 그릇 상태: 짜장면 담김
식사 후 그릇 상태: 비워짐
이동시 그릇 상태: 비워짐

비동기

: 특정 로직의 실행이 끝날 때까지 기다려주지 않고 나머지 코드 먼저 실행

  • 반드시 순서대로는 아님!

배달시 그릇 상태: 짜장면 담김
이동시 그릇 상태: 짜장면 담김
식사 후 그릇 상태: 비워짐

동기식이라면 배달원은 손님이 식사 마칠 때까지 기다린 후 다음 장소로 배달

  • 주문자의 식사와 배달부의 배달은 다른 쓰레드!
  • 비동기 프로그래밍은 쓰레드나 프로세스가 여럿 도는 것을 의미

Why?

화면에서 데이터를 서버로 요청했을 때 응답을 마냥 기다릴수만은 없다...

동기 비동기 비교


동기는 동일한 기찻길 위의 기차들

비동기는 다른 선로에 기차 배치

자바스크립트 비동기


비동기로 주어진 일을 마친 후 실행되는 함수: 콜백 함수
여기선 setTimeout() 안의 anonymous 함수인 function ()이 콜백함수!

  • setTimeout()은 Web API의 한 종류! seconds * 1000만큼 기다렸다가 로직 실행한닷!! (1000 === 1초)

  • But 자바 스크립트는 단일 쓰레드로 돌아가는 synchronous한 언어인데 비동기 프로그래밍이 어떻게 가능할까?

    JS 뿐만아니라 Web API라는 것도 같이 동작한다
    Web API는 타이머, 파일에서 데이터를 읽어오기 등의 시간 관련 작업을 담당


JS의 단일 쓰레드에서 비동기로 처리된 함수를 읽으면 Web API로 비동기 코드가 처리된 후 테스크 큐로 들어가 다시 JS의 단일 쓰레드로 들어간다

콜백지옥과 해결책

  • Synchronous callback example
console.log('1');
setTimeout(() => console.log('2'), 1000);
console.log('3');

function printImmedeately(print){
    print();
} // declaration은 위로 가겠져?
printImmedeately(() => console.log('sync'));
  • Asynchronous callback example
function printWithDelay(print, timeout){
    setTimeout(print, timeout);
} // declaration은 위로 가겠져?
printWithDelay(() => console.log('async'), 2000);
  • Callback Hell example
class UserStorage{
    loginUser(id, password, onSuccess, onError){
        setTimeout(() => {
            if(
                (id === 'comet' && password === 'sweet') ||
                (id === 'glen' && password === 'morange')
            ){
                onSuccess(id);
            } else{
                onError(new Error('not found'));
            }
        }, 2000);
    }

    getRoles(user, onSuccess, onError){
        setTimeout(() =>{
            if (user === 'comet'){
                onSuccess({name: 'comet', role: 'admin'});
            } else{
                onError(new Error('no access'));
            }
        })
    }
}

const userStorage = new UserStorage();
const id = prompt('Enter your id');
const password = prompt('Enter you password');
userStorage.loginUser(
    id,
    password,
    user => {
        userStorage.getRoles(
            user,
            userWithRole =>{
                alert(
                    `Hello ${userWithRole.name}, you have a ${userWithRole.role} role`
                    );
            },
            error => {
                console.log(error);
            }
        );
    },
    error => {
        console.log(error);
    }
)

고3때 수학선생님 찾는 프로그램


콜백함수의 연속... 콜백지옥...가독성 떨어지고 디버깅은 죽음이다...

Promise

  • Promise is a JavaScript object for asnychronous operation
  • State: pending -> fulfilled or rejected
  • Producer vs Consumer

    체이닝 방식으로 비동기 작업을 순차적으로 처리가능(가독성 좋아짐)
    But Promise는 I.E에서 지원 X (이젠노상관)

Producer

  • when new Promise is created, the executor runs automatically
  • executor는 promise가 생성되자마자 바로 실행된다는 점 주의!
const promise = new Promise((resolve, reject) => {
    // doing some heavy work (network, read files)
    console.log('doing something');
    setTimeout(() => {
        //resolve('comet'); 
        reject('no network');
    }, 2000);
});

Consumer

  • then, catch, finally
  • Promise chaining - promise 안에 promise 선언 가능
promise
    .then(value => { // resolve는 then에서 걸린다 - resolve의 값 받아옴
        console.log(value);
    })
    .catch(error => { // error는 catch에서 걸린다 - reject의 값 받아옴
        console.log(error);
    })
    .finally(() => { // finally는 성공/실패 상관없이 무조건 실행되는 구문 
        console.log('finally');
    });

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

    fetchNumber
        .then(num => num*2) // then으로 resolve의 값을 연쇄적으로 불러오게 됨
        .then(num => num*3)
        .then(num => { // then의 param으로 new Promise도 들어올 수 있다
            return new Promise((resolve, reject) =>{
                setTimeout(() => resolve(num - 1), 1000);
            });
        })
        .then(num => console.log(num));

Error Handling

catch 문을 에러날 곳 직전에 써야 에러처리를 잘할 수 있음

// 4. Error Handling
const getHen = () => // 이거 에로우함수 왜 {}로 감싸면 안됨??
    new Promise((resolve, reject) => {
        setTimeout(() => resolve('Chicken'), 1000);
    });

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

const getFri = egg =>
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${egg} -> fri`), 1000);
    });

getHen()
    .then(getEgg)
    .catch(error => { // catch문을 에러날 곳 직전에 써야 핸들링이 잘 된다
        return 'pork';
    })
    .then(getFri)
    .then(console.log)
    ;
//.then(egg => getFri(egg)) - 한가지를 받아서 넣는 경우 이처럼 생략ㄱㄴ

callback hell --> promise

앞선 콜백지옥을 promise문으로 고친 코드이다

'use strict';

// Callback Hell example
class UserStorage{
    loginUser(id, password) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if(
                    (id === 'comet' && password === 'sweet') ||
                    (id === 'glen' && password === 'morange')
                ){
                    resolve(id);
                } else{
                    reject(new Error('not found'));
                }
            }, 2000);
        });
    };
    getRoles(user) {
        return new Promise((resolve, reject) => {
            setTimeout(() =>{
                if (user === 'comet'){
                    resolve({name: 'comet', role: 'admin'});
                } else{
                    reject(new Error('no access'));
                }
            }, 1000);
        });
    };
}



const userStorage = new UserStorage();
const id = prompt('Enter your id');
const password = prompt('Enter you 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: syntatic sugar(arrow 함수처럼 문법적 기능은 그대로인데 읽는 사람이 직관적으로 더 쉽게 읽을 수 있게 만드는 것)
  • clear style of using promise XD
  • ES7부터 추가됨
  • await으로 앞의 코드가 마치기 전에 그 다음으로 넘어가는 것을 방지

Async

// async를 붙이면 이 코드블럭이 자동으로 promise문으로 바뀌는 느낌이다
async function fetchUser() {
    // do network request in 10 secs...(대충 오래걸리는 작업)
    // 동기로 하면 여기서 10초이상을 머무르며 다른 동작이 실행이 안되겠지?
    // + promise쓸 때 resolve, reject없이 그냥 return쓰면 promise에서 pending으로 머문다
    return 'comet';
}

const user = fetchUser()
user.then(console.log);
console.log(user);

Await

function delay(ms){
    return new Promise(resolve => setTimeout(resolve, ms));
}
// await 없이 2초 딜레이를 주는 코드를 짜려면 이처럼 해야함
// function getApple{
//     return delay(2000)
//     .then(() => 'banana');
// }
async function getApple(){
    await delay(2000);
    //throw 'error';
    return 'apple';
}

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

but apple과 banana를 동시에 호출하는건 어케 구현하지이?

  • promise로 짤 때
    콜백지옥급 promise지옥이 펼쳐진다
    사과와 바나나는 독립적이라 각각 비동기로 실행돼야하는데 순차적으로 실행됨
function pickFruits() {
    return getApple()
    .then(apple => {
        return getBanana()
        .then(banana => `${apple} + ${banana}`);
    })
}
pickFruits().then(console.log);
  • async & await로 짤 때
    try-catch문으로 일반적인 형태로 에러 헨들링이 가능함
    간결해졌지만 여전히 각각 독립적으로 실행은 안됨
async function pickFruits() {
    try{
        const apple = await getApple();
        const banana = await getBanana();
    } catch{
        console.log("error occured");
    }
    return `${apple} + ${banana}`;
}
pickFruits().then(console.log);
  • best way so far
    promise는 선언과 동시에 executor가 실행된다는 점을 이용하여
    getApple과 getBanana를 비동기적으로 선언과 동시에 실행시킨다
async function pickFruits() {
    const applePromise = getApple();
    const bananaPromise = getBanana();
    const apple = await applePromise;
    const banana = await bananaPromise;
    return `${apple} + ${banana}`;
}
pickFruits().then(console.log);

Useful Promise APIs

  • Promise.all : promise 배열을 전달하면 모든 promise를 병렬적으로 다 모아줌
function pickAllFruits() {
    return Promise.all([getApple(), getBanana()])
    .then(fruits => fruits.join(' + '));
}
pickAllFruits().then(console.log);
  • Promise.race: 여러 promise 중 먼저 값을 리턴하는 애만 전달됨
function pickOnlyOne() {
    return Promise.race([getApple(), getBanana()])
}
pickOnlyOne().then(console.log);

Callback hell --> async & await

class UserStorage{
    loginUser(id, password){
        return new Promise((resolve, reject) => {
            setTimeout(() =>{
                if(
                    (id === 'comet' && password === 'sweet') ||
                    (id === 'glen' && password === 'morange')
                ){
                    resolve(id);
                } else{
                    reject(new Error('no access'));
                }
            }, 1000);
        });
    }

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

    async getUserWithRole(user, password){
        const id = await this.loginUser(user, password);
        const role = await this.getRoles(id);
        return role;
    }
}

const userStorage = new UserStorage();
const id = prompt('Enter your id');
const password = prompt('Enter you password');

userStorage
    .getUserWithRole(id, password)
    .catch(console.log)
    .then(console.log);

출처

https://www.youtube.com/watch?v=m0icCqHY39U
https://joshua1988.github.io/web-development/javascript/javascript-asynchronous-operation/

profile
똘멩이

0개의 댓글