: 비동기 호출. 비동기 호출이란 동기 호출이랑 반대되는 개념이다. 동기는 순차적으로 호출되고 실행되어지지만, 비동기는 그렇지않다. 자바 스크립트를 배울때 비동기는 우리가 필요할때 실행시키는것을 비동기 호출이라고 부른다.
일상적인 예로는 커피를 주문할때와 같다.
동기는 한 손님이 커피를 주문하고, 그 주문한 커피가 나올때까지 그 손님이 비키지 않는다. 그러면 그 뒤에 있는 손님들은 계속 기다려야만 한다. 우리는 이것을 blocking되었다고 말한다.
그러나 비동기는 모든 손님들이 음료가 나올때까지 기다릴 필요없이, 자기가 원하는 음료를 주문하면된다. 그러면 주문하는 도중에 음료가 다 만들어지고, 음료를 뺄 수 있다. 이렇게 주문을하고, 완료되면 음료를 받아 갈 수 있는것을 우리는 unblocking이라고 부른다.
실생활에서 예제를 봤다면, 우리가 사용하는 컴퓨터 세계에서는 애니메이션, 네트워크와 연결 , 파일을 불러올때, 백그라운드 실행, 로딩 창 등과 같은곳에서 사용되어진다.
위의 비동기 호출을 하기 위해서 우리가 할 수 있는 방법중 첫번째는 콜백이다.
let callbackFunc = (string, callback) => { setTimeout( () => { console.log(string) callback() },3000) } let printStr = () => { callbackFunc('hi',()=> { callbackFunc('hello', () =>{ callbackFunc('world',() =>{}) }) }) } printStr() // 'hi' // 'hello' // 'world'
3초마다 하나씩 출력되는것을 볼 수 있다. 그러나 콜백을 10번을 실행한다면 ?
let printStr = () => { callbackFunc('hi',()=> { callbackFunc('hello', () =>{ callbackFunc('world',() =>{ callbackFunc('4times',() =>{ callbackFunc('5times',() => { callbackFunc('6times',() => { callbackFunc('7times',() => { ......... }) }) }) }) }) }) }) }
이처럼 가독성이 좋지않지않은 코드를 보게된다. 우리는 이것을 '콜백지옥' 또는 'callback - hell'이라고 부른다.
그래서 우리는 대안으로 promise를 사용한다.
Promise와 async&await를 공부하기전에 알아야 할 것
- 모든 비동기는 Promise를 리턴한다.
프로미스는 하나의 인스턴스와 같다. 우리가 인스턴스를 만들때 new를 사용한거처럼 프로미스도 new를 사용해야한다. 또 프로미스는 인자로 함수를 받는데, 그 함수는 resolve와 reject라는 함수 인자를 받는다.
아래의 코드는 비동기인 프로미스를 리턴한는 함수를 나타내는 코드다.
let promise = (string) =>{ return new Promise((resolve, reject) => { setTimeout( () => { console.log(string) resolve() }, 3000) }) } // let printStr = () =>{ promise('hi') .then(()=>{ return promise('hello') }) .then(() => { return promise('word') }) } printStr();
콜백을 사용할때와, 프로미스를 사용할때를 비교해보자.
이렇게 Promise와 then 메소드를 사용하여 좀 더 간략하게 표현 할 수 있다.
MDN에서 promise를 더 정확히 알아보면, (promise() 사용법)
Promise는 프로미스가 생성될 때 꼭 알 수 있지는 않은 값을 위한 대리자로, 비동기 연산이 종료된 이후의 결과값이나 실패 이유를 처리하기 위한 처리기를 연결할 수 있도록 합니다. 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있습니다. 다만 최종 결과를 반환하지는 않고, 대신 프로미스를 반환해서 미래의 어떤 시점에 결과를 제공합니다.
다시말해, Promise 객체는 비동기 호출 후 미래에 fulfilled 또는 reject의 값을 나타냅니다.
Promise는 다음 중 하나의 상태를 가집니다.
대기(pending): 이행하거나 거부되지 않은 초기 상태.
이행(fulfilled or resolve): 연산이 성공적으로 완료됨.
거부(rejected): 연산이 실패함.
*추가
settled : fulfilled 상태이든 reject 상태이든 결론이 난 상태를 말함.
Promise.all(iterable)
iterable 내의 모든 프로미스가 이행한 뒤 이행하고, 어떤 프로미스가 거부하면 즉시 거부하는 프로미스를 반환합니다. 반환된 프로미스가 이행하는 경우 iterable 내의 프로미스가 결정한 값을 모은 배열이 이행 값입니다. 반환된 프로미스가 거부하는 경우 iterable 내의 거부한 프로미스의 이유를 그대로 사용합니다. 이 메서드는 여러 프로미스의 결과를 모을 때 유용합니다
function getNewsAndWeatherAll() { let news = fetch(newsURL); let weather = fetch(weatherURL); // return Promise.all([news, weather]) .then(resArr => resArr.map(data => data.json())) .then(jsonArr => Promise.all(jsonArr)) .then(res => { return { news: res[0].data, weather: res[1] }; }); }
then()
: then은 프로미스 인스턴스가 리턴하는 값을 인자로 받으면서, 그 프로미스가 실행된 후에 바로 실행되어지는 메소드이다. 주로 resolve의 값을 받으면 가끔 에러값을 반환하기도한다(?).
catch()
: 이 메소드는 주로 마지막에 사용되어진다. 프로미스 인스턴스가 then메소드를 모두 실행하고, 에러값이 뜨면 에러를 내보내기위해서 catch메소드를 사용한다.
프로미스도 콜백 지옥처럼 프로미스 지옥을 만들 수 있다.
그러나 이걸 잘 해결할 수 있는 방법은 return 을 잘 활용하면 해결 할 수 있다.
아래 처럼 새로 실행 시켜야할 함수를 실행 시키고, then메소드를 사용하면 된다.
: async와 await는 JS의 비동기 처리 방법 중 하나로 promise의 단점을 보완하고, 가독성이 더 좋게 만들어 줄 수 있다.
이 문법을 사용할때 주의해야 할 점.
async
를 적어서 이 함수는 비동기 함수라는것을 명시 해줘야한다.await
를 사용하여 비동기적으로 처리한다.
await는 JS가 Promise가 settled 되기 전까지 기다리게 하는 역할을 한다.
이렇게 async 와 await를 사용하면, 동기적으로 사용한것처럼 코드의 가독성을 높일 수 있다. 아래의 그림이 예시다.
: async & await에서 예외(오류)를 처리하기 위해서는 try catch 문법을 사용하면된다.
async function foo(){ try{ if(false){ const test = await randomFunc(); console.log(test); } } catch(err){ console.log(err); } }
참고하면 좋은 사이트
1. https://javascript.info/async-await
2. https://developers.google.com/web/fundamentals/primers/async-functions
3. https://blog.bitsrc.io/understanding-asynchronous-javascript-the-event-loop-74cd408419ff (advanced)