JS

[JS][Udemy] 비동기식 JavaScript

winter17 2023. 4. 13. 12:28

► Call Stack

  • 스크립트가 함수를 호출하면, 해석기가 콜스텍으로 함수를 추가해서 함수를 불러낸다
  • stack : 후입선출 구조
const multiply = (x, y) => x * y;

const square = x => multiply(x, x);

const isRightTriangle = (a, b, c) => (
    square(a) + square(b) === square(c)
)
console.log("BEFORE")
isRightTriangle(3, 4, 5)
console.log(isRightTriangle(3, 4, 5)) // true → 25 === 25
console.log("DONEEEE!")

 

 

 

► JavaScript is a single thread + web API

console.log('Sending request to server!')
setTimeout(() => { // 브라우저야 3초 뒤에 출력해줄래?
	console.log('I am at the end of the file!')
}, 3000)
console.log('I am at the end of the file!')

// Sending request to server!
// I am at the end of the file!
// (3초 후)I am at the end of the file!

  setTimeout을 web API에 넘겨서 3초 타이머를 실행한 후 설정한 시간이 지나면 콜백을 실행해서 출력한다

 

 

 

► CallbackHell

  • 콜백을 사용해야 한다는 건 우리가 해결할 수 없는 것 혹은 적어도 정해지지 않은 시점에 코드를 실행하기 위한 메커니즘을 제공해야 한다는 것
  • setTimeout을 한 번만 사용하는 경우에는 간편하지만 5~6개 종속적인 작업이 있다면 코드의 중첩이 많아지도 가독성이 떨어진다. 이때, 비동기Promises가 필요하다
// 콜백 중첩을 이런식으로 하면 코드가 너무 길어짐
setTimeout(() => {
    document.body.style.backgroundColor = 'red';
    setTimeout(() => {
        document.body.style.backgroundColor = 'orange';
        setTimeout(() => {
            document.body.style.backgroundColor = 'yellow';
            setTimeout(() => {
                document.body.style.backgroundColor = 'green';
                setTimeout(() => {
                    document.body.style.backgroundColor = 'blue';
                }, 1000)
            }, 1000)
        }, 1000)
    }, 1000)
}, 1000)
// 변경시킬 배경색 함수를 짜고
const delayedColorChange = (newColor, delay, doNext) => {
    setTimeout(() => {
        document.body.style.backgroundColor = newColor;
        doNext && doNext();
    }, delay)
}

// 색상과 타임을 중첩해서 콜백 함수 작성
// STILL A LOT OF NESTING!!!
delayedColorChange('red', 1000, () => {
    delayedColorChange('orange', 1000, () => {
        delayedColorChange('yellow', 1000, () => {
            delayedColorChange('green', 1000, () => {
                delayedColorChange('blue', 1000, () => {

                })
            })
        })
    })
});
// 하나 이상의 콜백이 있는 경우
// 콜백이 실패할 경우 다음 콜백 실행
searchMoviesAPI('amadeus', () => {
    saveToMyDB(movies, () => {
        //if it works, run this:
    }, () => {
        //if it doesn't work, run this:
    })
}, () => {
    //if API is down, or request failed
})

 

 

► Promises

  • 어떤 연산, 비동기 연산이 최종적으로 완료 혹은 성공했는지 실패했는지 알려주는 객체
// THE CALLBACK VERSION
const fakeRequestCallback = (url, success, failure) => {
    const delay = Math.floor(Math.random() * 4500) + 500;
    setTimeout(() => {
        if (delay > 4000) {
            failure('Connection Timeout :(')
        } else {
            success(`Here is your fake data from ${url}`)
        }
    }, delay)
}
// 콜백 요청
fakeRequestCallback('books.com/page1',
    function (response) {
        console.log("IT WORKED!!!!")
        console.log(response)
        fakeRequestCallback('books.com/page2',
            function (response) {
                console.log("IT WORKED AGAIN!!!!")
                console.log(response)
                fakeRequestCallback('books.com/page3',
                    function (response) {
                        console.log("IT WORKED AGAIN (3rd req)!!!!")
                        console.log(response)
                    },
                    function (err) {
                        console.log("ERROR (3rd req)!!!", err)
                    })
            },
            function (err) {
                console.log("ERROR (2nd req)!!!", err)
            })
    }, function (err) {
        console.log("ERROR!!!", err)
    })
// THE PROMISE VERSION 
const fakeRequestPromise = (url) => {
    return new Promise((resolve, reject) => {
        const delay = Math.floor(Math.random() * (4500)) + 500;
        setTimeout(() => {
            if (delay > 4000) {
                reject('Connection Timeout :(')
            } else {
                resolve(`Here is your fake data from ${url}`)
            }
        }, delay)
    })
}

// 위의 함수를 호출했을 때 아래와 같은 promises 객체가 나타남
// pending : 무언가를 기다림
// rejected : 실패함
// resolved : 해결됨

fakeRequestPromise('yelp.com/api/coffee/page1')
    .then(() => { // resolved일 때
        console.log("IT WORKED!!!!!! (page1)")
        fakeRequestPromise('yelp.com/api/coffee/page2')
            .then(() => { // resolved일 때
                console.log("IT WORKED!!!!!! (page2)")
                fakeRequestPromise('yelp.com/api/coffee/page3')
                    .then(() => { // resolved일 때
                        console.log("IT WORKED!!!!!! (page3)")
                    })
                    .catch(() => { // rejected일 때
                        console.log("OH NO, ERROR!!! (page3)")
                    })
            })
            .catch(() => { // rejected일 때
                console.log("OH NO, ERROR!!! (page2)")
            })
    })
    .catch(() => { // rejected일 때
        console.log("OH NO, ERROR!!! (page1)")
    })
// THE PROMISE VERSION 
const fakeRequestPromise = (url) => {
    return new Promise((resolve, reject) => {
        const delay = Math.floor(Math.random() * (4500)) + 500;
        setTimeout(() => {
            if (delay > 4000) {
                reject('Connection Timeout :(')
            } else {
                resolve(`Here is your fake data from ${url}`)
            }
        }, delay)
    })
}

// THE CLEANEST OPTION WITH THEN/CATCH
// RETURN A PROMISE FROM .THEN() CALLBACK SO WE CAN CHAIN!
fakeRequestPromise('yelp.com/api/coffee/page1')
    .then((data) => {
        console.log("IT WORKED!!!!!! (page1)")
        console.log(data) // Here is your fake data from ${url}
        return fakeRequestPromise('yelp.com/api/coffee/page2')
    })
    .then((data) => {
        console.log("IT WORKED!!!!!! (page2)")
        console.log(data) // Here is your fake data from ${url}
        return fakeRequestPromise('yelp.com/api/coffee/page3')
    })
    .then((data) => {
        console.log("IT WORKED!!!!!! (page3)")
        console.log(data) // Here is your fake data from ${url}
    })
    .catch((err) => {
        console.log("OH NO, A REQUEST FAILED!!!")
        console.log(err) // Connection Timeout :(
    })

 

 

► Creating promises

// promises version
const fakeRequest = (url) => {
    return new Promise((resolve, reject) => { // new promises는 두 개의 인수를 가진다
        const rand = Math.random();
        setTimeout(() => {
            if (rand < 0.7) {
                resolve('YOUR FAKE DATA HERE');
            }
            reject('Request Error!');
        }, 1000)
    })
}

fakeRequest('/dogs/1')
    .then((data) => {
        console.log("DONE WITH REQUEST!")
        console.log('data is:', data)
    })
    .catch((err) => {
        console.log("OH NO!", err)
    })
// callback version : 중첩이 많아져서 가독성이 떨어짐
const delayedColorChange = (newColor, delay, doNext) => {
    setTimeout(() => {
        document.body.style.backgroundColor = newColor;
        doNext && doNext();
    }, delay)
}

delayedColorChange('red', 1000, () => {
    delayedColorChange('orange', 1000, () => {
        delayedColorChange('yellow', 1000, () => {
            delayedColorChange('green', 1000, () => {
                delayedColorChange('blue', 1000, () => {
                    delayedColorChange('indigo', 1000, () => {
                        delayedColorChange('violet', 1000, () => {

                        })
                    })
                })
            })
        })
    })
});
// promises version
const delayedColorChange = (color, delay) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            document.body.style.backgroundColor = color;
            resolve();
        }, delay)
    })
}


delayedColorChange('red', 1000)
    .then(() => delayedColorChange('orange', 1000))
    .then(() => delayedColorChange('yellow', 1000))
    .then(() => delayedColorChange('green', 1000))
    .then(() => delayedColorChange('blue', 1000))
    .then(() => delayedColorChange('indigo', 1000))
    .then(() => delayedColorChange('violet', 1000))

 

 

► ASYNC Functions

  • promises 구문을 더 깔끔하게 만들어준다
  • ASYNC : 함수를 비동기 함수로 선언하는 키워드
const sing = async () => { // 함수 선언시 async키워드를 사용하면 자동으로 promise를 반환함
    throw "OH NO, PROBLEM!"
    return 'LA LA LA LA' 
}

sing()
    .then(data => {
        console.log("PROMISE RESOLVED WITH:", data) // data = 'LA LA LA LA'
    })
    .catch(err => {
        console.log("OH NO, PROMISE REJECTED!")
        console.log(err) // OH NO, PROBLEM!
    })
const login = async (username, password) => {
    if (!username || !password) throw 'Missing Credentials'
    if (password === 'corgifeetarecute') return 'WELCOME!'
    throw 'Invalid Password'
}

login('todd', 'corgifeetarecute')
    .then(msg => {
        console.log("LOGGED IN!")
        console.log(msg)
    })
    .catch(err => {
        console.log("ERROR!")
        console.log(err)
    })

 

 

► Await Functions

  • async처럼 함수 선언시 함수 내부에서만 사용 가능
  • await 키워드는 비동기 함수의 실행을 일시 정지시킴
const delayedColorChange = (color, delay) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            document.body.style.backgroundColor = color;
            resolve();
        }, delay)
    })
}

// delayedColorChange('red', 1000)
//     .then(() => delayedColorChange('orange', 1000))
//     .then(() => delayedColorChange('yellow', 1000))
//     .then(() => delayedColorChange('green', 1000))
//     .then(() => delayedColorChange('blue', 1000))
//     .then(() => delayedColorChange('indigo', 1000))
//     .then(() => delayedColorChange('violet', 1000))


async function rainbow() { // await를 사용하면 promise가 결과를 낼 때까지 기다림
    await delayedColorChange('red', 1000)
    await delayedColorChange('orange', 1000)
    await delayedColorChange('yellow', 1000)
    await delayedColorChange('green', 1000)
    await delayedColorChange('blue', 1000)
    await delayedColorChange('indigo', 1000)
    await delayedColorChange('violet', 1000)
    return "ALL DONE!"
}

// rainbow().then(() => console.log("END OF RAINBOW!")) 위의 코드가 전부 실행되고나면 해당 문구 출력

async function printRainbow() { // 출력될 코드를 함수로 작성해서 바로 위 코드랑 같은 출력 결과를 나타냄
    await rainbow(); // rainbow 함수가 전부 실행될 때까지 기다렸다가
    console.log("END OF RAINBOW!") // 출력
}

printRainbow(); // printRainbow 호출

 

 

► 비동기 함수의 오류 처리하기

 

const fakeRequest = (url) => {
    return new Promise((resolve, reject) => {
        const delay = Math.floor(Math.random() * (4500)) + 500;
        setTimeout(() => {
            if (delay > 2000) {
                reject('Connection Timeout :(')
            } else {
                resolve(`Here is your fake data from ${url}`)
            }
        }, delay)
    })
}

// 오류 처리 : try ~ catch
async function makeTwoRequests() {
    try { // 오류가 될 코드를 적으면
        let data1 = await fakeRequest('/page1');
        console.log(data1);
        let data2 = await fakeRequest('/page2');
        console.log(data2);
    } catch (e) { // 어떻게 처리할지 정의, e는 에러를 나타냄
        console.log("CAUGHT AN ERROR!")
        console.log("error is:", e)
    }

}